C’è un modo per contare con i macro?

Voglio creare una macro che stampa “Hello” un numero specificato di volte. È usato come:

many_greetings!(3); // expands to three `println!("Hello");` statements 

Il modo ingenuo per creare quella macro è:

 macro_rules! many_greetings { ($times:expr) => {{ println!("Hello"); many_greetings!($times - 1); }}; (0) => (); } 

Tuttavia, questo non funziona perché il compilatore non valuta le espressioni; $times - 1 non viene calcolato, ma alimentato come una nuova espressione nella macro.

Mentre il sistema macro ordinario non ti consente di ripetere molte volte l’espansione della macro, non c’è alcun problema nell’usare un ciclo for nella macro:

 macro_rules! many_greetings { ($times:expr) => {{ for _ in 0..$times { println!("Hello"); } }}; } 

Se hai davvero bisogno di ripetere la macro, devi esaminare macro procedurali / plug -in del compilatore (che a partire da 1.4 sono instabili e un po ‘più difficili da scrivere).

Modifica: Ci sono probabilmente modi migliori per implementare questo, ma ho speso abbastanza tempo per questo per oggi, quindi ecco qui. repeat! , una macro che in realtà duplica un blocco di codice un numero di volte:

main.rs

 #![feature(plugin)] #![plugin(repeat)] fn main() { let mut n = 0; repeat!{ 4 { println!("hello {}", n); n += 1; }}; } 

lib.rs

 #![feature(plugin_registrar, rustc_private)] extern crate syntax; extern crate rustc; use syntax::codemap::Span; use syntax::ast::TokenTree; use syntax::ext::base::{ExtCtxt, MacResult, MacEager, DummyResult}; use rustc::plugin::Registry; use syntax::util::small_vector::SmallVector; use syntax::ast::Lit_; use std::error::Error; fn expand_repeat(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box { let mut parser = cx.new_parser_from_tts(tts); let times = match parser.parse_lit() { Ok(lit) => match lit.node { Lit_::LitInt(n, _) => n, _ => { cx.span_err(lit.span, "Expected literal integer"); return DummyResult::any(sp); } }, Err(e) => { cx.span_err(sp, e.description()); return DummyResult::any(sp); } }; let res = parser.parse_block(); match res { Ok(block) => { let mut stmts = SmallVector::many(block.stmts.clone()); for _ in 1..times { let rep_stmts = SmallVector::many(block.stmts.clone()); stmts.push_all(rep_stmts); } MacEager::stmts(stmts) } Err(e) => { cx.span_err(sp, e.description()); DummyResult::any(sp) } } } #[plugin_registrar] pub fn plugin_registrar(reg: &mut Registry) { reg.register_macro("repeat", expand_repeat); } 

aggiunto a Cargo.toml

 [lib] name = "repeat" plugin = true 

Nota che se davvero non vogliamo fare il looping, ma espandendoci in fase di compilazione, dobbiamo fare cose come richiedere numeri letterali. Dopotutto, non siamo in grado di valutare variabili e chiamate di funzione che fanno riferimento ad altre parti del programma in fase di compilazione.

Per quanto ne so, no. Il linguaggio macro si basa sulla corrispondenza dei pattern e sulla sostituzione delle variabili, e valuta solo le macro.

Ora puoi implementare il conteggio con la valutazione: è semplicemente noioso … vedi il box

 macro_rules! many_greetings { (3) => {{ println!("Hello"); many_greetings!(2); }}; (2) => {{ println!("Hello"); many_greetings!(1); }}; (1) => {{ println!("Hello"); many_greetings!(0); }}; (0) => (); } 

Sulla base di questo, sono abbastanza sicuro che si possa inventare un set di macro per “contare” e richiamare varie operazioni ad ogni passaggio (con il conteggio).