2014-04-14 8 views
6

Fondamentalmente, ci sono due parti a questa domanda:È possibile dichiarare le variabili proceduralmente usando le macro di Rust?

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

  2. È possibile combinare stringhe per generare nuovi nomi di variabili in una macro di Rust? !

Per esempio, qualcosa come:

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

Calling espandere (hi) ovvia non riesce a causa hi è un identificatore sconosciuta; ma puoi farlo in qualche modo?

ie. L'equivalente in C di qualcosa di simile:

#include <stdio.h> 
#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!

considerare questo blocco di ruggine:

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

È ora hanno un valore preso in prestito con una durata di vita limitata in modo esplicito (marker) che non utilizza tutta la vita struttura, ma che possiamo garantire esiste per l'intero ambito di la chiamata ffi; allo stesso tempo non ci imbattiamo in errori oscuri in cui un * viene 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 venga effettuato all'interno di un blocco sicuro.

(vedi anche Why are all my pointers pointing to the same place with to_c_str() in rust?)

L'uso di una macro che può dichiarare variabili temporanee per questo scopo sarebbe notevolmente alleviare i problemi che ho in lotta con il compilatore. Ecco perché voglio farlo.

risposta

9

Sì, è possibile passare identificatore arbitrario in una macro e sì, è possibile concatenare gli identificatori in un nuovo identificatore utilizzando concat_idents!() macro:

#![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, perché concat_idents!() è di per sé una macro , non è possibile utilizzare questo identificatore concatenato ovunque è possibile utilizzare l'identificatore semplice, solo in alcuni punti come nell'esempio sopra, e questo, a mio parere, è un ENORME svantaggio. 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.

BTW, se capisco correttamente la tua idea, non hai davvero bisogno di concatenare gli identificatori per ottenere nomi univoci. I macro ruggine, contrariamente a quelli C, sono hygienic. 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 supporre che questo codice dovrebbe funzionare:

macro_rules! test { 
    ($body:expr) => ({ let x = 10; $body }) 
} 

fn main() { 
    let y = test!(x + 10); 
    println!("{}", y); 
} 

Cioè, creiamo una variabile x e porre espressione dopo la sua dichiarazione.E 'quindi naturale pensare che x in test!(x + 10) si riferisce a quella variabile dichiarata dalla macro, e tutto dovrebbe andare bene, ma in realtà questo codice non compilerà:

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ò che serve è l'unicità di gente del posto, quindi puoi tranquillamente non fare nulla e usare i nomi che vuoi, saranno automaticamente unici. Questo è explained nel tutorial macro, anche se trovo l'esempio piuttosto confuso.

+1

concat_idents potrebbe essere rimosso, era già dotato di funzionalità [# 13295] (https://github.com/mozilla/rust/pull/13295) e [# 13294] (https://github.com/mozilla/rust/issues/13294) – Arjan

+0

Abbiamo davvero bisogno di questa funzione simile a ## in C. Devo costantemente scrivere macro in cui gli utenti mi danno due nomi perché string concat è così limitato. –

1

Nei casi in cui concat_idents non funziona (che nella maggior parte dei casi mi piacerebbe usarlo) la modifica del problema dagli identificatori concatenati all'utilizzo di spazi dei nomi funziona.

Cioè, anziché il codice non-lavoro:

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

l'utente può assegnare un nome al namespace, e quindi hanno nomi predefiniti come illustrato 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.

Problemi correlati