rustrust-macros

Can you write an anaphoric macro in Rust?


Rust macros have hygiene, so this doesn't work:

macro_rules! λ {
    ($e:expr) => {
        |it| $e
    }
}

fn main() {
    // this looks like it should emit:
    // let f = |it| it > 0
    // but it's not quite that
    let f = λ!(it > 0);

    f(42);
}

This doesn't work because of syntax context (there's probably a better link for this) so that the it > 0 in the intended body of the lambda will not actually find the |it| that's being used as the lambda parameter.

This is, usually, what you want. Macro hygiene is a great feature.

However, what if I really want to write an anaphoric macro like this? Does Rust offer a mechanism for doing so, or is the only solution in Rust to require that the macro also provide the ident separately - as in λ!(it, it > 0)?

For this particular case, this is silly since that's longer than the normal lambda to begin with, but this is just an example so bear with me. As an example from a different programming language, Racket has hygiene by default but also provides a syntax-parameter (the aif example in there does this).


Solution

  • You can do this with procedural macros since they are unhygienic.

    macro crate

    [package]
    name = "mymacros"
    version = "0.1.0"
    edition = "2021"
    
    [lib]
    proc-macro = true
    
    [dependencies]
    quote = "1.0.36"
    syn = { version = "2.0.65", features = ["full"] }
    
    use proc_macro::TokenStream;
    
    use quote::quote;
    use syn::Expr;
    
    #[proc_macro]
    pub fn λ(item: TokenStream) -> TokenStream {
        let expr = syn::parse_macro_input!(item as Expr);
    
        quote! { |it| #expr }.into()
    }
    

    main crate

    [dependencies]
    mymacros = { path = "./mymacros" }
    
    use mymacros::λ;
    
    fn main() {
        let f = λ!(it > 0);
    
        dbg!(f(42));
    }
    
    [src/main.rs:6:5] f(42) = true
    

    However, procedural macros are daunting to get into and can be complicated even for simple things. If you want to continue using declarative macros (i.e. macro_rules!) then there is the unhygienic2 crate that does that for you:

    macro_rules! λ {
        ($e:expr) => {
            |it| unhygienic2::unhygienic! { $e }
        };
    }