How to use `self` (or any variable) in macro generated function body

Hello everybody,

I'm trying to use a macro to generate structs and corresponding impls for a specific trait -- see the code below or the playground at Rust Playground, but I'm failing by trying to use a variable in a block.

Specifically, the macro create_protocol should create a struct Tcp and implement the trait Protocol`s single method send with the code block that is passed as the macro`s second parameter. Now this fails, because the block is trying to use self which - as far as I understand - does not work due to macro hygiene: the self in the block is not the same as the self in the implementation.

Interestingly, the generated code is correct. If I expand the macro via rustc -Z unstable-options --pretty expanded macro_test.rs > macro_expanded.rs, I do receive the following error message:

macro_test.rs:18:38: 18:42 error: `self` is not available in a static method. Maybe a `self` argument is missing? [E0424]
macro_test.rs:18     let TcpConnect(ref challenge) = *self;
                                                      ^~~~
macro_test.rs:17:1: 21:4 note: in this expansion of create_protocol! (defined in macro_test.rs)
macro_test.rs:18:38: 18:42 help: run `rustc --explain E0424` to see a detailed explanation
error: aborting due to previous error 

But the expanded code does compile without errors: rustc macro_expanded.rs.

There are open issues regarding the use of self in macros like `self` variable in macros is broken (regression) · Issue #15682 · rust-lang/rust · GitHub, but I don't want to add to the discussion there. I'm just interested in how to solve this problem with the current capabilities of Rust macros.

Thanks for your help in advance,
Lukas


use std::io::Error;

pub trait Protocol {
    fn send(self: &Self) -> Result<Vec<u8>, Error>;
}

macro_rules! create_protocol {
    ($protocol_name: ident, $send_code: block) => (
        pub struct $protocol_name(pub Vec<u8>);

        impl<'a> Protocol for $protocol_name {
            fn send(&self) -> Result<Vec<u8>, Error> $send_code
        }
    )
}

create_protocol!(Tcp, {
    let Tcp(ref data) = *self;
    // Do stuff with data
    Ok(vec![0u8; 10])
});

fn main() {}

3 Likes

I happens because of macro hygiene – any identifier used in an expression passed to a macro is assumed to come from outside of a macro, unless it's passed to the macro as an identifier or in a pattern. So, the workaround woud be to pass self explicitely as an identifier:

...
macro_rules! create_protocol {
    ($protocol_name: ident, $sel:ident $send_code: block) => (
        pub struct $protocol_name(pub Vec<u8>);

        impl<'a> Protocol for $protocol_name {
            fn send(&$sel) -> Result<Vec<u8>, Error> $send_code
        }
    )
}

create_protocol!(Tcp, self {
    let Tcp(ref data) = *self;
    // Do stuff with data
    Ok(vec![0u8; 10])
});
4 Likes

Fantastic. That works -- not very nice, but it resolves my problem.

Thank you @krdln!

2 Likes