È ansible dichiarare le variabili proceduralmente usando le macro di Rust?

Fondamentalmente, ci sono due parti per questa domanda:

  1. Puoi passare un identificatore sconosciuto a una macro in Rust ?

  2. Puoi combinare stringhe per generare nuovi nomi di variabili in una macro di Rust?

Ad esempio, qualcosa come:

macro_rules! expand( ($x:ident) => ( let mut x_$x = 0; ) ) 

Calling expand! (Hi) ovvio fallisce perché hi è un identificatore sconosciuto; ma puoi farlo in qualche modo?

vale a dire. L’equivalente in C di qualcosa di simile:

 #include  #define FN(Name, base) \ int x1_##Name = 0 + base; \ int x2_##Name = 2 + base; \ int x3_##Name = 4 + base; \ int x4_##Name = 8 + base; \ int x5_##Name = 16 + base; int main() { FN(hello, 10) printf("%d %d %d %d %d\n", x1_hello, x2_hello, x3_hello, x4_hello, x5_hello); return 0; } 

Perché dici, che idea terribile. Perché vorresti mai farlo?

Sono contento che tu abbia chiesto!

Considera questo blocco di rust:

 { let marker = 0; let borrowed = borrow_with_block_lifetime(data, &marker); unsafe { perform_ffi_call(borrowed); } } 

Ora hai un valore preso in prestito con una durata (marker) esplicitamente limitata che non utilizza una durata della struttura, ma che possiamo garantire esiste per l’intero ambito della chiamata ffi; allo stesso tempo non ci imbattiamo in errori oscuri in cui un * è de-referenziato in modo non sicuro all’interno di un blocco non sicuro e quindi il compilatore non lo rileva come un errore, nonostante l’errore sia stato commesso all’interno di un blocco sicuro .

(vedi anche Perché tutti i miei puntatori puntano allo stesso posto con to_c_str () in rust? )

L’uso di una macro che può dichiarare variabili temporanee per questo scopo faciliterebbe considerevolmente i problemi che ho combattuto con il compilatore. Ecco perché voglio farlo.

Sì, puoi passare un identificatore arbitrario in una macro e sì, puoi concatenare gli identificatori in un nuovo identificatore usando la concat_idents!() :

 #![feature(concat_idents)] macro_rules! test { ($x:ident) => ({ let z = concat_idents!(hello_, $x); z(); }) } fn hello_world() { } fn main() { test!(world); } 

Tuttavia, per quanto ne so, poiché concat_idents!() sé una macro, non è ansible utilizzare questo identificatore concatenato ovunque si possa usare l’identificatore semplice, solo in alcuni punti come nell’esempio sopra, e questo, secondo me, è un enorme inconveniente. Proprio ieri ho provato a scrivere una macro che poteva rimuovere un sacco di codice nel mio codice, ma alla fine non ero in grado di farlo perché le macro non supportano il posizionamento arbitrario di identificatori concatenati.

A proposito, se capisco correttamente la tua idea, non hai davvero bisogno di concatenare gli identificatori per ottenere nomi univoci. I macro rust, contrariamente a quelli C, sono igienici . Ciò significa che tutti i nomi delle variabili locali introdotte all’interno di una macro non perderanno allo scope in cui viene chiamata questa macro. Ad esempio, si potrebbe presumere che questo codice funzionerebbe:

 macro_rules! test { ($body:expr) => ({ let x = 10; $body }) } fn main() { let y = test!(x + 10); println!("{}", y); } 

Cioè, creiamo una variabile x e mettiamo un’espressione dopo la sua dichiarazione. È quindi naturale pensare che x in test!(x + 10) riferisca a quella variabile dichiarata dalla macro, e tutto dovrebbe andare bene, ma in realtà questo codice non verrà compilato:

 main3.rs:8:19: 8:20 error: unresolved name `x`. main3.rs:8 let y = test!(x + 10); ^ main3.rs:3:1: 5:2 note: in expansion of test! main3.rs:8:13: 8:27 note: expansion site error: aborting due to previous error 

Quindi, se tutto ciò di cui hai bisogno è l’unicità dei locali, allora puoi tranquillamente non fare nulla e usare i nomi che desideri, saranno automaticamente unici. Questo è spiegato nel tutorial macro, anche se trovo l’esempio piuttosto confuso.

Nei casi in cui concat_idents non funziona (che è la maggior parte dei casi mi piacerebbe usarlo) la modifica del problema dagli identificatori concatenati all’utilizzo di namespace funziona.

Cioè, invece del codice non funzionante:

 macro_rules! test { ($x:ident) => ({ struct concat_idents!(hello_, $x) {} enum contact_idents!(hello_, $x) {} }) } 

L’utente può nominare lo spazio dei nomi e quindi avere nomi predefiniti come mostrato di seguito:

 macro_rules! test { ($x:ident) => ({ mod $x { struct HelloStruct {} enum HelloEnum {} } }) } 

Ora hai un nome basato sull’argomento della macro. Questa tecnica è utile solo in casi specifici.