I'm using a custom #[derive]-like with macros, how do I handle lifetimes?


#1

My library uses some traits named Vertex, UniformBlock and BufferContent.
These traits are unsafe and dangerous to implement (because for example some methods return the offsets of each member in the struct), so it shouldn’t be left to the user.

The ideal solution would be to have the #[derive_Vertex], #[derive_UniformBlock] and #[derive_BufferContent] attributes.
But since plugins aren’t stable, I wrote the implement_vertex!, implement_uniform_block! and implement_buffer_content! macros.

They work like this:

unsafe trait Foo {
    // some very dangerous methods in there
}

struct Bar { a: u32 }

// obviously the macro is in reality in my library
macro_rules! implement_foo(
    ($s:ident) => (
        unsafe impl Foo for $s {}
    );
);

implement_foo!(Bar);

My problem is that recently I had the need for implementing this trait for structs that have lifetimes. And that’s problematic.

The macros don’t need to know there is a lifetime, but they generate impl Foo for Bar, while it should be impl<'a> Foo for Bar<'a>.
I tried various tricks with token trees and such, but I can’t manage to write a macro that would work in both situations.

For the moment my work-around is to duplicate the whole macro rule, so that I have two definitions: one for structs with a lifetime and one for structs without a lifetime, like this:

unsafe trait Foo {
    // some very dangerous methods in there
}

struct Bar1 { a: u32 }
struct Bar2<'a> { a: &'a u32 }

macro_rules! implement_foo(
    ($s:ident) => (
        unsafe impl Foo for $s {}
    );
    
    ($s:ident <$l:tt>) => (
        unsafe impl<'a> Foo for $s<'a> {}
    );
);

implement_foo!(Bar1);
implement_foo!(Bar2<'a>);

But it’s obviously not ideal (especially because the definitions are a hundred lines long each), so is there a better solution?


#2

The PR that illustrates the problem: https://github.com/tomaka/glium/pull/1084


#3

You can do this:

unsafe trait Foo {
    // some very dangerous methods in there
}

struct Bar1 { a: u32 }
struct Bar2<'a> { a: &'a u32 }

macro_rules! as_item {
    ($i:item) => {$i};
}

macro_rules! implement_foo(
    (#x $s:ident [$($gs:tt)*]) => {
        as_item! {
            unsafe impl <$($gs)*> Foo for $s<$($gs)*> {}
        }
    };

    ($s:ident) => (
        implement_foo!(#x $s []);
    );
    
    ($s:ident <$l:tt>) => (
        implement_foo!(#x $s [$l]);
    );
);

implement_foo!(Bar1);
implement_foo!(Bar2<'a>);

fn main() {}

You break the macro into two parts: a “frontend” parser and a “backend” expander. < and > don’t work as matchers, so you rewrite all generics to use something else instead (like [..]). It also helps that empty generics (i.e. <>) are just fine and the same as them not being there at all.

If your macro is more complicated than that, then I’m afraid you’ll probably have to go for a muncher, which I would point you to the article for, but I haven’t finished writing it yet… sigh


#4

Ah good idea, thanks!
I didn’t know you could write Foo<>.


#5

But it’s not working :frowning:

<glium macros>:2:19: 2:23 error: expected ident, found `'a`
<glium macros>:2 unsafe impl < $ ( $ gs ) * > $ crate:: buffer:: Content for $ struct_name < $
                                   ^~~~

#6

Did you use the as_item! wrapper around the output?


#7

I have absolutely no idea why this as_item! is needed, but it seems to work. Thanks.