2016-03-28 9 views
1

Ich versuche, eine große Reihe von Elementen mit dem gleichen Initialisierer zu initialisieren. 64 Elemente ist nur ein Beispiel - ich möchte es mindestens 16k machen. Leider ein einfachesVerwenden eines Makros zum Initialisieren eines großen Array von Nicht-Copy-Elementen

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

wird nicht funktionieren, weil die AllocatedMemory Struktur nicht nicht implementiert 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]; 
             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

Also versuchte ich Makros ohne Erfolg:

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

Die Fehlermeldung ist

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 

Gibt es eine Möglichkeit, dieses Array zu initialisieren, ohne jeden Initialisierer ausschneiden und einfügen zu müssen?

+0

Verwandte: [Gibt es eine Möglichkeit mit Makros zählen] (http://stackoverflow.com/questions/33751796/is-there-a-way -zum-zählen-mit-makros), tl; dr -> nicht mit regulären Makros. –

Antwort

2

Das Problem ist, dass the expansion of a macro absolutely must be a complete and independently valid grammar element. Sie können nicht mehr auf a, b erweitern, als Sie auf 42 + erweitern können. Es gibt auch keine Möglichkeit, Arrays in Rust (statisch) zu verketten oder zu kontrahieren; Der gesamte Array-Initialisierer muss auf einen Schritt erweitert werden.

Dies kann mithilfe von Makros mit push-down accumulation erfolgen. Der Trick besteht darin, dass Sie den noch nicht syntaktisch gültigen partiellen Array-Ausdruck down die Rekursion übergeben, anstatt auf dem Weg zurück zu konstruieren. Wenn Sie das Ende der Erweiterung erreichen, geben Sie den nun vollständigen Ausdruck auf einmal aus.

Hier ist ein Makro, das Arrays der Länge 0 bis 8 und Potenzen von 2 bis zu 64 unterstützt:

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[..]); 
} 

Die Strategie ist hier zu multiplizieren, um die Größe der Eingabe auf Zweierpotenzen, und fügen Sie die Rest für Zweiermächte. Dies ist, um das Makro Rekursionslimit abzuwehren (ich glaube, der Standardwert ist 64), indem Sie sicherstellen, $n fällt schnell in Wert.

Nur um die häufige Follow-up-Frage zu vermeiden: nein, können Sie dies nicht mit Arithmetik vereinfachen; Sie können nicht tun Arithmetik in Makros. :)

Nachtrag: Wenn Sie nicht sicher sind, wie das funktioniert, Sie -Z trace-macros zu rustc passieren können beim Kompilieren und jeden Makro Aufruf sehen, die erweitert werden. Mit array![1; 6] als Beispiel, erhalten Sie so etwas wie dieses:

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

Das Problem mit diesen Makros ist, dass das erstere keine gültigen syntaktischen Formen in Rust erzeugt - zwei Ausdrücke, die durch ein Komma kombiniert werden, sind keine gültige Form für sich. Die Tatsache, dass es in eckige Klammern in einem anderen Makro "injiziert" wird, ist irrelevant.

Offen gesagt, ich weiß nicht, wie es richtig mit regelmäßigen Arrays zu tun. Das Fehlen von Zahlen als generische Parameter ist ein bekanntes Problem, das viele nützliche Muster ausschließt. Wenn sie getragen wurden, zum Beispiel, wäre es möglich, eine Funktion zu haben, wie diese:

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

die ein Array von beliebiger Größe erzeugt, ist es mit dem Ergebnis des Funktionsaufrufes Füllung:

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

Aber leider gibt es das in Rust noch nicht. Sie müssen stattdessen dynamische Strukturen wie Vec verwenden. Sie können auch versuchen, arrayvec, die eine Vec-ähnliche API für einige Arrays fester Größe bietet; Wenn Sie es verwenden, können Sie Folgendes tun:

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

Das ist eine wirklich coole Lib - leider habe ich vergessen zu erwähnen, dass ich von unsicherem Territorium Abstand nehmen will, vor allem für den 3rd-Party-Code – hellcatv

+0

brauche ich eigentlich nur eine Listenschnittstelle ... Ich frage mich, ob ich ein paar Makros zum Generieren verwenden könnte eine Liste der Größe N kann eine Art aufdringliche Sache mit einer Option nächste – hellcatv

+0

@hellcatv Wenn Sie STD-Bibliothek auch nur im Geringsten verwenden, werden Sie auf unsicheren Code angewiesen. Fast alle nützlichen Abstraktionen (Sammlungen, Speicherverwaltung, I/O, Threading usw.) basieren auf unsicherem Code und können nicht wirklich ohne diesen Code ausgeführt werden. Es ist nichts falsch daran, sichere Schnittstellen um unsichere Interna zu verwenden. Sie müssen nicht selbst unsicher sein. –