2015-06-02 14 views
10

Ho una macro che crea una struttura e un mucchio di funzioni di supporto e implementazioni di tratti. La cosa interessante per questa domanda è:Come si crea un tipo parametrizzato da una macro?

macro_rules! make_struct { 
    ($name: ident) => { 
     struct $name; 
    } 
} 

Questo funziona come ci si aspetta:

make_struct!(MyStruct); 

Se voglio fare un tipo parametrizzato però, io sono fuori di fortuna:

make_struct!(AnotherStruct<T: SomeTrait>); 

test.rs:8:27: 8:28 error: no rules expected the token `<` 
test.rs:8 make_struct!(AnotherStruct<T: SomeTrait>); 

il nome del struct è un ident quindi non può cambiare solo che nelle args macro (ad esempio per ty):

test.rs:3:16: 3:21 error: expected ident, found `MyStruct` 
test.rs:3   struct $name; 

Quindi, come posso scrivere questa macro per poterla gestire entrambe? O ho bisogno di separare quelli? In quest'ultimo caso, che aspetto ha la macro?

risposta

4

Dopo la parola chiave struct, il parser si aspetta un albero di token ident, che può essere seguito da < e altro; non è sicuramente quello che vuole (come un esempio del perché non funzionerebbe, (Trait + Send + 'static) è un nome valido, ma il struct (Trait + Send + 'static); chiaramente non ha senso).

Per supportare i generici, è necessario produrre più regole.

macro_rules! make_struct { 
    ($name:ident) => { 
     struct $name; 
    }; 
    ($name:ident<$($t:ident: $constraint:ident),+>) => { 
     struct $name<$($t: $constraint),+>; 
    } 
} 

Come si osservano senza dubbio, supportando tutto ciò che il parser accetterebbe in quella posizione è quasi impossibile; macro_rules non è così intelligente. Esiste, tuttavia, questo strano trucco (gli analizzatori di codici statici lo odiano!) Che consente di prendere una sequenza di token tree e trattarla come una normale definizione struct. Esso utilizza solo un po 'di più di indirezione con macro_rules:

macro_rules! item { 
    ($item:item) => ($item); 
} 
macro_rules! make_struct { 
    ($name:ident) => { 
     struct $name; 
    }; 
    ($name:ident<$($tt:tt)*) => { 
     item!(struct $name<$($tt)*;); 
    }; 
} 

Nota che a causa di eccesso di zelo, ident attualmente non permettono sequenza di ripetizione ($(…)) per seguire immediatamente, così ti verrà bloccato mettendo qualche segno albero tra il ident e la ripetizione, ad es $name:ident, $($tt:tt)* (codice AnotherStruct, <T: SomeTrait>) o $name:ident<$(tt:tt)* =>struct $name<$($tt)*;. Il valore di questo è ridotto dal fatto che non è possibile separarlo abbastanza facilmente per ottenere i singoli tipi generici, che è necessario fare per cose come l'inserimento di marcatori PhantomData.

Potrebbe essere utile passare l'intero articolo struct; passa come tipo item (come un fn, enum, use, trait, & c. farebbe).

Problemi correlati