Code generation from macros

Hey folks. I'm working on a library with lots of functions that look like:

    pub fn get_noise_type(&self) -> Result<i32, SynthizerError> {
        self.handle().get_i(Property::NoiseType.to_i32().unwrap())
    }

    pub fn set_noise_type(&self, value: i32) -> Result<(), SynthizerError> {
        self.handle()
            .set_i(Property::NoiseType.to_i32().unwrap(), value)
    }

If possible, I'd like to generate that code via macro, something like:

i!(noise_type, Property::NoiseType);

To be clear, this is an internal API, and the end goal is to generate code identical to the above. I just have a number of different property types, some of which perform some hairy and unsafe operations, so really want this code generated once and only once.

I have this macro:

macro_rules! i {
    ($name:tt, $property:path) => {
        pub fn get_$name(&self) -> Result<i32, SynthizerError> {
            self.handle().get_i($property.to_i32().unwrap())
        }

        pub fn set_$name(&self, value: i32) -> Result<(), SynthizerError> {
            self.handle().set_i($property.to_i32().unwrap(), value)
        }
    };
}

This reports:

error: expected one of `(` or `<`, found `noise_type`                                                                   
   --> synthizer-rs\src\lib.rs:435:20                                                                                   
    |                                                                                                                   
435 |         pub fn get_$name(&self) -> Result<i32, SynthizerError> {                                                  
    |                    ^^^^^ expected one of `(` or `<`                                                               

Is what I'm attempting even possible? If so, how do I make it work? I tried $name as an ident as well but it gave the same result.

Thanks.

You may want to investigate using mashup:

macro_rules! i {
    ($name:tt, $property: path) => {
        mashup! {
            m["get"] = get_ $name;
            m["set"] = set_ $name;
        }
        m! {
            pub fn "get"(&self) -> Result<i32, SynthesizerError> {
                //...
            }
            pub fn "set"(&self) -> Result<i32, SynthesizerError> {
                 //...
             }
        }
    };
}

Concatenating idents is currently unstable and limited in rust's stdlib macros. Using crates like these lets you circumvent those restrictions.

2 Likes

Macros by example (macro_rules!) can’t create new identifiers; they can only use ones that they are given. So get_$name isn’t going to work without some external help. You can either use a crate like paste that provides this ability or restructure the code you’re producing. For example, something like this has similar ergonomics and can be easily produced via macro:

pub struct PropertyRef<'hnd> {
    handle: &'hnd Handle,
    prop: Property
}

impl<'hnd> PropertyRef<'hnd> {
    fn get(&self)->Result<i32, SynthesizerError> {
        self.handle.get_i(self.prop.to_i32().unwrap())
    }

    fn set(&self, val:i32)-> Result<(), SynthesizerError> {
        self.handle.set_i(self.prop.to_i32.unwrap(), val)
    }
}


impl MainStruct {
    fn prop_ref(&self, prop: Property)->PropertyRef<'_> {
        PropertyRef { handle: self.handle(), prop }
    }

    // generated by macro
    pub fn noise_type(&self)->PropertyRef<'_> {
        self.prop_ref(Property::NoiseType)
    }
}

Thanks, paste was exactly what I needed!

One minor issue, for which there may not be a fix. I have these macros:


macro_rules! i {
($name:ident, $property:path) => {
paste! {
pub fn [<get_ $name>](&self) -> Result<i32, SynthizerError> {
self.handle().get_i($property.to_i32().unwrap())
}
pub fn [<set_ $name>](&self, value: i32) -> Result<(), SynthizerError> {
self.handle().set_i($property.to_i32().unwrap(), value)
}
}
};
}
macro_rules! ti {
($name:ident, $property:path) => {
paste! {
fn [<get_ $name>](&self) -> Result<i32, SynthizerError> {
self.handle().get_i($property.to_i32().unwrap())
}
fn [<set_ $name>](&self, value: i32) -> Result<(), SynthizerError> {
self.handle().set_i($property.to_i32().unwrap(), value)
}
}
};
}

The difference being that i! is meant for use in impl and includes pub, while ti! is for traits and can't. Is there any way to conditionally include the pub qualifier somehow? No biggy if not--that's the only property macro I need to implement for traits, so if I need to preserve that duplication then that's fine. I'm just working with an API that's in lots of flux, so the more boilerplate I can generate, the better off I am.

Thanks again.

Please
run
rustfmt
on
your
code
so
that
it
isn't
all
left-aligned.

You can edit your prior post that isn't formatted by clicking on the pencil icon under the post.

3 Likes

You could do something like this:

macro_rules! prop_accessors {
    ($viz:vis $name:ident, $property:path) => { /* ... */ }
}

macro_rules! i {
    ($name:ident, $property:path) 
    => { prop_accessors!( pub $name, $property ); }
}

macro_rules! ti {
    ($name:ident, $property:path) 
    => { prop_accessors!( $name, $property ); }
}