visual una sirve que pasos paso para macro hacer ejemplos crear como rust initializer-list static-initializer

rust - sirve - Usar una macro para inicializar una gran variedad de elementos no copiados



pasos para crear una macro en excel (2)

El problema con estas macros es que la primera no produce formas sintácticas válidas en Rust: dos expresiones combinadas por una coma no son una forma válida en sí misma. El hecho de que se "inyecte" entre corchetes en otra macro es irrelevante.

Francamente, no sé cómo hacerlo correctamente con arreglos regulares. La ausencia de números como parámetros genéricos es un problema bien conocido que excluye muchos patrones útiles. Si fueran compatibles, por ejemplo, sería posible tener una función como esta:

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

que crea una matriz de tamaño arbitrario, llenándola con el resultado de invocación de función:

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

Pero, por desgracia, eso no está disponible en Rust todavía. En su lugar, debe usar estructuras dinámicas como Vec . También puedes probar arrayvec , que proporciona una API similar a Vec para algunas matrices de tamaño fijo; usándolo puedes hacer algo como esto:

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]

Intento inicializar una gran variedad de elementos con el mismo inicializador. 64 elementos es solo un ejemplo: quiero que sea al menos 16k. Desafortunadamente un simple

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

no funcionará porque la estructura de AllocatedMemory no implementa 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]; ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Así que probé macros en vano:

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()); }

El mensaje de error es

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

¿Hay alguna manera de inicializar esta matriz sin cortar y pegar cada inicializador?


El problema es que la expansión de una macro debe ser un elemento gramatical completo e independientemente válido . No puedes expandir a a, b más de lo que puedes expandir a 42 + . Tampoco hay forma de (concatenadamente) concatenar o mantener matrices en Rust; todo el inicializador de matriz debe expandirse en un solo paso.

Esto se puede hacer usando macros con acumulación push-down . El truco consiste en pasar la expresión de matriz parcial aún no sintácticamente válida por la recursión, en lugar de construir en el camino de regreso. Cuando llegue al final de la expansión, emitirá la expresión ahora completa a la vez.

Aquí hay una macro que admite matrices de longitud 0 a 8, y potencias de 2 hasta 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[..]); }

La estrategia aquí es multiplicar el tamaño de la entrada en las potencias de dos y agregar el resto para las no potencias de dos. Esto es para evitar el límite de macro recursividad (creo que el valor predeterminado es 64) asegurándose de que $n caiga en valor rápidamente.

Solo para anticipar la frecuente pregunta de seguimiento: no , no se puede simplificar esto con aritmética; no puedes hacer aritmética en macros. :)

Adición : si no está seguro de cómo funciona esto, puede pasar -Z trace-macros a rustc al compilar y ver cada invocación de macro que se expande. Usando array![1; 6] array![1; 6] como ejemplo, obtienes algo como esto:

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 , ] }