2016-03-28 3 views
1

Я пытаюсь инициализировать большой массив элементов с тем же инициализатором. 64 элемента - всего лишь пример - я хочу сделать его как минимум 16k. К сожалению, простойИспользование макроса для инициализации большого массива элементов без копирования

let array : [AllocatedMemory<u8>; 64] = [AllocatedMemory::<u8>{mem:&mut []};64]; 

не будет работать, потому что AllocatedMemory структура не реализует Copy

error: the trait `core::marker::Copy` is not implemented for the type `AllocatedMemory<'_, u8>` [E0277] 
let array : [AllocatedMemory<u8>; 64] = [AllocatedMemory::<u8>{mem:&mut []}; 64]; 
             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

Так что я попытался макросы без толку:

struct AllocatedMemory<'a, T: 'a> { 
    mem: &'a mut [T], 
} 

macro_rules! init_memory_helper { 
    (1, $T : ty) => { AllocatedMemory::<$T>{mem: &mut []} }; 
    (2, $T : ty) => { init_memory_helper!(1, $T), init_memory_helper!(1, $T) }; 
    (4, $T : ty) => { init_memory_helper!(2, $T), init_memory_helper!(2, $T) }; 
    (8, $T : ty) => { init_memory_helper!(4, $T), init_memory_helper!(4, $T) }; 
    (16, $T : ty) => { init_memory_helper!(8, $T), init_memory_helper!(8, $T) }; 
    (32, $T : ty) => { init_memory_helper!(16, $T), init_memory_helper!(16, $T) }; 
    (64, $T : ty) => { init_memory_helper!(32, $T), init_memory_helper!(32, $T) }; 
} 

macro_rules! init_memory { 
    (1, $T : ty) => { [init_memory_helper!(1, $T)] }; 
    (2, $T : ty) => { [init_memory_helper!(2, $T)] }; 
    (4, $T : ty) => { [init_memory_helper!(4, $T)] }; 
    (8, $T : ty) => { [init_memory_helper!(8, $T)] }; 
    (16, $T : ty) => { [init_memory_helper!(16, $T)] }; 
    (32, $T : ty) => { [init_memory_helper!(32, $T)] }; 
    (64, $T : ty) => { [init_memory_helper!(64, $T)] }; 
} 

fn main() { 
    let array: [AllocatedMemory<u8>; 64] = init_memory!(64, u8); 
    println!("{:?}", array[0].mem.len()); 
} 

сообщение об ошибке

error: macro expansion ignores token `,` and any following 
    (64, $T : ty) => { init_memory_helper!(32, $T), init_memory_helper!(32, $T) }; 
note: caused by the macro expansion here; the usage of `init_memory_helper!` is likely invalid in expression context 

Есть ли способ инициализировать этот массив без вырезания и вставки каждого инициализатора?

+0

Связанные: [Есть ли способ подсчета с помощью макросов] (http://stackoverflow.com/questions/33751796/is-there-a-way -to-count-with-macros), tl; dr -> не с регулярными макросами. –

ответ

2

Проблема в том, что the expansion of a macro absolutely must be a complete and independently valid grammar element. Вы не можете расширить до a, b больше, чем сможете расширить до 42 +. В Rust также нет способа (статически) конкатенации или cons-массивов; весь инициализатор массива должен быть расширен до один шаг.

Это можно сделать с помощью макросов с push-down accumulation. Фокус в том, что вы передаете не все-синтаксически-действительное выражение частичного массива вниз рекурсии, вместо того, чтобы строить на обратном пути. Когда вы достигнете нижней части расширения, вы сразу же испускаете теперь полное выражение.

Вот макрос, который поддерживает массивы длины от 0 до 8, и полномочия 2 до 64:

macro_rules! array { 
    (@accum (0, $($_es:expr),*) -> ($($body:tt)*)) 
     => {array!(@as_expr [$($body)*])}; 
    (@accum (1, $($es:expr),*) -> ($($body:tt)*)) 
     => {array!(@accum (0, $($es),*) -> ($($body)* $($es,)*))}; 
    (@accum (2, $($es:expr),*) -> ($($body:tt)*)) 
     => {array!(@accum (0, $($es),*) -> ($($body)* $($es,)* $($es,)*))}; 
    (@accum (3, $($es:expr),*) -> ($($body:tt)*)) 
     => {array!(@accum (2, $($es),*) -> ($($body)* $($es,)*))}; 
    (@accum (4, $($es:expr),*) -> ($($body:tt)*)) 
     => {array!(@accum (2, $($es,)* $($es),*) -> ($($body)*))}; 
    (@accum (5, $($es:expr),*) -> ($($body:tt)*)) 
     => {array!(@accum (4, $($es),*) -> ($($body)* $($es,)*))}; 
    (@accum (6, $($es:expr),*) -> ($($body:tt)*)) 
     => {array!(@accum (4, $($es),*) -> ($($body)* $($es,)* $($es,)*))}; 
    (@accum (7, $($es:expr),*) -> ($($body:tt)*)) 
     => {array!(@accum (4, $($es),*) -> ($($body)* $($es,)* $($es,)* $($es,)*))}; 
    (@accum (8, $($es:expr),*) -> ($($body:tt)*)) 
     => {array!(@accum (4, $($es,)* $($es),*) -> ($($body)*))}; 
    (@accum (16, $($es:expr),*) -> ($($body:tt)*)) 
     => {array!(@accum (8, $($es,)* $($es),*) -> ($($body)*))}; 
    (@accum (32, $($es:expr),*) -> ($($body:tt)*)) 
     => {array!(@accum (16, $($es,)* $($es),*) -> ($($body)*))}; 
    (@accum (64, $($es:expr),*) -> ($($body:tt)*)) 
     => {array!(@accum (32, $($es,)* $($es),*) -> ($($body)*))}; 

    (@as_expr $e:expr) => {$e}; 

    [$e:expr; $n:tt] => { array!(@accum ($n, $e) ->()) }; 
} 

fn main() { 
    let ones: [i32; 64] = array![1; 64]; 
    println!("{:?}", &ones[..]); 
} 

Стратегия здесь заключается в умножении размера входа на степени двойки, и добавить остаток для не-полномочий двух. Это должно остановить предел рекурсии (я считаю, что по умолчанию 64), убедившись, что $n быстро падает.

Чтобы избежать частого вопроса о последующей деятельности: no, вы не можете упростить это с помощью арифметики; вы не можете сделать арифметикой в ​​макросах. :)

Добавление: Если вы не знаете, как это работает, вы можете передать -Z trace-macros в rustc при компиляции и увидеть каждый вызов макроса, который получает расширенный. Использование array![1; 6] в качестве примера, вы получите что-то вроде этого:

array! { 1 ; 6 } 
array! { @ accum (6 , 1) -> () } 
array! { @ accum (4 , 1) -> (1 , 1 ,) } 
array! { @ accum (2 , 1 , 1) -> (1 , 1 ,) } 
array! { @ accum (0 , 1 , 1) -> (1 , 1 , 1 , 1 , 1 , 1 ,) } 
array! { @ as_expr [ 1 , 1 , 1 , 1 , 1 , 1 , ] } 
1

Проблема с этими макросами заключается в том, что первая не создает в Rust синтаксических форм - два выражения, объединенные запятой, не являются действительной формой. Тот факт, что он «вводится» в квадратные скобки в другом макросе, не имеет значения.

Честно говоря, я не знаю, как правильно это сделать с помощью обычных массивов. Отсутствие чисел в качестве общих параметров является хорошо известной проблемой, которая исключает множество полезных шаблонов. Если бы они были поддержаны, например, можно было бы иметь функцию, как этот:

fn make_array<T, N: usize, F>(f: F) -> [T; N] where F: FnMut() -> T 

, которая создает массив произвольного размера, заполняя его с результатом вызова функции:

let array: [_; 64] = make_array(|| AllocatedMemory::<u8>{ mem: &mut [] }) 

Но, увы, в Rust пока нет такой вещи. Вместо этого вы должны использовать динамические структуры, такие как Vec. Вы также можете попробовать arrayvec, который предоставляет API-интерфейс Vec-like для некоторых массивов фиксированного размера; используя его, вы можете сделать что-то вроде этого:

let mut array = ArrayVec::<[_; 64]>::new(); 
for _ in 0..array.len() { 
    array.push(AllocatedMemory::<u8>{ mem: &mut [] }); 
} 
let array = array.into_inner(); // array: [AllocatedMemory<u8>; 64] 
+0

Это действительно классный lib - к сожалению, я забыл упомянуть, что хочу воздержаться от небезопасной территории, особенно для стороннего кода – hellcatv

+0

Мне действительно нужен интерфейс списка ... Интересно, могу ли я использовать несколько макросов для генерации список размера N, возможно, какая-то навязчивая вещь с опцией next – hellcatv

+0

@hellcatv, если вы используете std-библиотеку даже в малейшей степени, вы будете зависеть от небезопасного кода. Почти все полезные абстракции (коллекции, управление памятью, ввод-вывод, потоки и т. Д.) Основаны на небезопасном коде и не могут быть выполнены без него. Нет ничего плохого в использовании безопасных интерфейсов вокруг небезопасных внутренних компонентов. Вам не нужно использовать 'unsafe' самостоятельно. –