How to match a closure in Rust

I have an enum ABC with 3 variants that share the same S struct, therefore I want to call the same method on them with the same variables. I know I could implement a trait, but this is a simplification of an example where A, B and C have other structs that make them different, but they share the same methods.

enum S{}

impl S{
    pub fn do_something(&self, number: u8) -> Result<(),()>{
    }
}

enum ABC{
    A(S),
    B(S),
    C(S)
}

//This is my attempt, but I don't know how to match for closures
macro_rules! for_each_stack {
    ($self:ident: &ABC, $b:block) => { 
        match $self {
            ABC::A(s) => $b(s),
            ABC::B(s) => $b(s),
            ABC::C(s) => $b(s),
        }
     }
}

impl ABC {
    //I'm trying to substitute this:
    pub fn do_something(&self, number: u8) {
        match self {
            ABC::A(s) => s.do_something(number),
            ABC::B(s) => s.do_something(number),
            ABC::C(s) => s.do_something(number),  
        }
    }
    //By this macro call
    pub fn do_something2(&self, number: u8) {
        for_each_stack!(self, |s| s.do_something(number))
    }
}

Playground

I'm having difficulties matching the macro for the closure. How should I do that? I tried with a block but this wont work of course.

Look that I'm also forcing $self:ident: &ABC so I can match over its variants.

You probably want $b:expr, since closures are expressions.

However, your macro contains several other syntax errors, and the code (both the already-existing non-macro code and the code that the macro expands to) contains type errors too.

Furthermore, this doesn't make any sense:

Macros aren't type checked. They are a syntactic abstraction, they are processed completely well before type checking can happen. They have nothing to do with the type system; they operate on a semi-parsed, untyped representation of the code (called token trees), and they don't understand the code. What you did there doesn't constrain the type of the self parameter, it literally just requires the macro invocation to contain the tokens : &ABC, and has no effect on type checking whatsoever.


On an unrelated note, there's no reason why this should be a macro at all. You should just write a higher-order function that is generic over the closure being invoked.

2 Likes

On an unrelated note, there's no reason why this should be a macro at all. You should just write a higher-order function that is generic over the closure being invoked.

what if I want to reimplement lots of S method in ABC? I don't see how it would work:

impl ABC {

    pub fn do_method<F>(&self, f: F)  -> Result<(),()>
    where F: Fn(?) { 

what goes in ?

Furthermore, this doesn't make any sense:

How would I be able to do

        match $self {
            ABC::A(s) => $b(s),
            ABC::B(s) => $b(s),
            ABC::C(s) => $b(s),
        }

without the macro knowing that self if of type &ABC?

What you did there doesn't constrain the type of the self parameter, it literally just requires the macro invocation to contain the tokens : &ABC , and has no effect on type checking whatsoever.

I think this is the solution for this part:

macro_rules! for_each_stack {
    ($self:ident, $b:pat) => { 
        match $self as &ABC {
            ABC::A(s) => $b(s),
            ABC::B(s) => $b(s),
            ABC::C(s) => $b(s),
        }
     }
}

Is it possible to match a macro against a closure like I'm trying to do?

for_each_stack!(self, |s| s.do_something(number))

The macro just subtitutes syntax. It doesn't know that self is of type &ABC, and it doesn't need to. The code that will subsequently be typechecked is not the macro definition itself; rather, it is the code the macro invocation is expanded to.

No it is not. That cast is completely unnecessary.

Yes, I have already answered this part of your question, you can use the expr fragment specifier instead of block, since closures are expressions.

Your current example probably doesn't demonstrate the problem, since in all of your variants, all of the associated values have exactly the same type. If this is the case, you could just extract a reference to the associated value and forward any eventual methods to it, like this:

impl ABC {
    fn as_inner(&self) -> &S {
        match *self {
            ABC::A(ref inner) => inner,
            ABC::B(ref inner) => inner,
            ABC::C(ref inner) => inner,
        }
    }

    fn forward<F, A, R>(&self, f: F, arg: A) -> R
        where
            F: FnOnce(&S, A) -> R,
    {
        f(self.as_inner(), arg)
    }

    // if there's a method `S::foo(&self, u8) -> f32`
    fn forward_foo(&self, x: u8) -> f32 {
        self.forward(S::foo, x)
    }

    // if there's a method `S::bar(&self, f64) -> String`
    fn forward_bar(&self, y: f64) -> String {
        self.forward(S::bar, y)
    }
}

However, it's uncommon and non-idiomatic to have enums with associated values of exactly the same type in each variant, so this is likely not what your actual code looks like. Please provide a more realistic example and a specific description of what you are trying to do.

1 Like

Thanks. Yes, your example with the forward function is great, but the reason that I'm avoiding it is because it's fixed on the number of arguments. Perhaps it's possible to do variadic arguments, I don't know.

The real reason I think I need a macro is because of this ip/tcp stack with different devices (virtual, tap, tun). Its methods are the same but the device is parameterized, but I wanted a stack that works with all 3 of them, so I made an enum:

pub enum SmolStackWithDevice<'a, 'b: 'a, 'c: 'a + 'b> {
    VirtualTun(SmolStack<'a, 'b, 'c, VirtualTunDevice>),
    Tun(SmolStack<'a, 'b, 'c, TunDevice>),
    Tap(SmolStack<'a, 'b, 'c, TapDevice>),
}

I think the only way to forward all the method from SmolStack to SmolStackWithDevice would be with a macro, unless variadic arguments are possible, or maybe doing foward_1 for 1 argument other than &S, forward_2 for 2 arguments, etc, up to 5 which would cover everything I guess.

The reason I did match $self as &ABC { is because I thought macros needed to know everything of the types inside the macro. For example,

macro_rules! call_on_self {
    ($F:ident) => {
        self.$F()
    };
}

struct F;
impl F {
    fn dummy(&self) {}
    fn test(&self) {
        call_on_self!(self, dummy);
    }
}

this does not work, I think, because the macro cannot know self has a dummy method.

It works if you fix the syntax error and the hygiene error:

macro_rules! call_on_self {
    ($receiver:ident, $F:ident) => {
        $receiver.$F()
    };
}

struct F;
impl F {
    fn dummy(&self) {}
    fn test(&self) {
        call_on_self!(self, dummy);
    }
}

Indeed it cannot know. It cannot ever know. Macros never know anything about the types in your code. They are not interested in types, they do not need types, and they do not work with types. At all. All they do is cut and paste code in a more intelligent and structured manner than simple text replacement would do. But the principle is laregly the same as text replacement.

When you invoke a macro, it is substituted with whatever it is defined to expand to, but neither its input nor its output is required to be syntactically or semantically valid Rust code, it only has to contain balanced parentheses (plus a few niche, relatively unimportant requirements). So in your code above, the code after macro expansion would look like:

struct F;
impl F {
    fn dummy(&self) {}
    fn test(&self) {
        self.dummy();
    }
}

which would in turn be typechecked by a later stage of the compiler. You could have as well invoked your macro like call_on_self!(self, nonexistent_method); and the expansion/substitution step would have worked just fine. Of course, it wouldn't have typechecked at that later stage, but by the time the compiler recognizes that there's no nonexistent_method defined on F, macros are already long gone.

Yes, that is right, I was missing exactly this piece of information.

1 Like

Maybe you're looking for something like this?

macro_rules! forward_call {
    ($receiver:ident . $F:ident ($($arg:expr),* $(,)?)) => {
        match $receiver {
            SmolStackWithDevice::VirtualTun(inner) => inner.$F($($arg),*),
            SmolStackWithDevice::Tun(inner) => inner.$F($($arg),*),
            SmolStackWithDevice::Tap(inner) => inner.$F($($arg),*),
        }
    };
}

pub enum SmolStackWithDevice<'a, 'b: 'a, 'c: 'a + 'b> {
    VirtualTun(SmolStack<'a, 'b, 'c, VirtualTunDevice>),
    Tun(SmolStack<'a, 'b, 'c, TunDevice>),
    Tap(SmolStack<'a, 'b, 'c, TapDevice>),
}

impl<'a, 'b: 'a, 'c: 'a + 'b> SmolStackWithDevice<'a, 'b, 'c> {
    fn test(&self) {
        forward_call!(self.dummy());
    }
}

After the macro expansion the impl block will look like this:

impl <'a, 'b: 'a, 'c: 'a + 'b> SmolStackWithDevice<'a, 'b, 'c> {
    fn test(&self, a: i32, b: i32, c: String) {
        match self {
            SmolStackWithDevice::VirtualTun(inner) => inner.dummy(a + b, c),
            SmolStackWithDevice::Tun(inner) => inner.dummy(a + b, c),
            SmolStackWithDevice::Tap(inner) => inner.dummy(a + b, c),
        };
    }
}

indeed it works very well. However, I noticed I need to reimplement all methods of SmolStackWithDevice's wrapped struct Stack (the type of the inner in our example) again in SmolStackWithDevice.

I think a macro like this would be better:

macro_rules! forward_call {
    ($receiver:ident, $closure:??????)) => {
        match $receiver {
            SmolStackWithDevice::VirtualTun(inner) => $closure(inner)),
            SmolStackWithDevice::Tun(inner) => $closure(inner)),
            SmolStackWithDevice::Tap(inner) => $closure(inner)),
        }
    };
}

then I call like this:

forward_call(stack_with_device, |inner: &Stack|{
    //Do something
})

Is it possible to match closures with fixed argument?

this would be the same as solving this:

macro_rules! call_this_closure {
    ($closure:???) => {
        let x = 0;
        $closure(x);
    };
}

fn main() {
    call_this_closure!(|x|println!("{}", x));
}

Thus:

macro_rules! forward_call {(
    $scrutinee:expr, $closure:expr $(,)?
) => ({
    use $crate::path::to::SmolStackWithDevice;
    match $scrutinee {
        | SmolStackWithDevice::VirtualTun(inner) => $closure(inner),
        | SmolStackWithDevice::Tun(inner) => $closure(inner),
        | SmolStackWithDevice::Tap(inner) => $closure(inner),
    }
})}
1 Like

I did this:

    forward_call!(self, |stack|{
        let mut socket = stack.0.get::<TcpSocket>(socket_handle);
        socket.tcp_socket_send(socket_handle, data)
    })

but I got

error[E0282]: type annotations needed
   --> src/networking/async_vpn_socket.rs:234:30
    |
234 |         forward_call!(self, |stack|{
    |                              ^^^^^ consider giving this closure parameter a type

I cannot annotate stack with a type because each variant of the enum has a different type:

 pub enum SmolStackWithDevice<'a> {
    VirtualTun((SocketSet<'a, 'a, 'a>, Interface<'a, 'a, 'a, VirtualTunDevice>)),
    Tun((SocketSet<'a, 'a, 'a>, Interface<'a, 'a, 'a, TunDevice>)),
    Tap((SocketSet<'a, 'a, 'a>, Interface<'a, 'a, 'a, TapDevice>)),
}

Closures aren't generic; they have to take concrete types as arguments. For some use cases you could take a trait object.

1 Like

But since they're using macros they can achieve "meta-genericity" through clever type inference hacks.

Applying the type inference trick to your situation, we'd get:

#[doc(hidden)] /** Not part of the public API */ pub
fn __feed__<T, R> (arg: T, f: impl FnOnce(T) -> R)
  -> R
{
    f(arg)
}

macro_rules! forward_call {(
    $scrutinee:expr, $closure:expr $(,)?
) => ({
    use $crate::path::to::SmolStackWithDevice;
    use $crate::path::to::__feed__;
    match $scrutinee {
        | SmolStackWithDevice::VirtualTun(inner) => __feed__(inner, $closure),
        | SmolStackWithDevice::Tun(inner) => __feed__(inner, $closure),
        | SmolStackWithDevice::Tap(inner) => __feed__(inner, $closure),
    }
})}

This ought to fix your issues.


The above trick works because a function taking an impl Fn…(…) -> … parameter is a good "retroactive type-inference" site that lets Rust infer the signature of the closure (this is, by the way, the same trick to get closures with a generic lifetime signature that return a value they borrow).


FWIW, there is another approach when we hit these type inference errors: drop the :expr support altogether, and "mock" closure support:

macro_rules! forward_call {(
    $scrutinee:expr,
    | $arg_name:pat | $body:expr $(,)?
) => ({
    use $crate::path::to::SmolStackWithDevice;
    use $crate::path::to::__feed__;
    match $scrutinee {
        | SmolStackWithDevice::VirtualTun($arg_name) => $body,
        | SmolStackWithDevice::Tun($arg_name) => $body,
        | SmolStackWithDevice::Tap($arg_name) => $body,
    }
})}
2 Likes

very nice idea, thanks!

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.