How do I early return in declarative macros and infer the return type?

636 views Asked by At

I have a macro, which just generates an instance of a struct, as below:

macro_rules! foo {
    () => {{
        let baz_val = baz();
        
        let bar_val = match bar() {
            Ok(val) => val,
            Err(err) => {
                return Err(err);
            }
        };
        
        Ok(Foo(baz_val, bar_val))
    }};
}

As you can see, I do an early return in macro depending on bar() function's result. That's, however, resolves as an error, saying:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/main.rs:23:24
   |
23 |                 return Err(err);
   |                        ^^^^^^^^ expected `()`, found enum `Result`
...
32 |     let foo_val = foo!();
   |                   ------ in this macro invocation
   |
   = note: expected unit type `()`
                   found enum `Result<_, String>`
   = note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0308`.

Here's also the permalink to the playground that has full minimum reproducible sample.

I think the problem here is type inference. I'd like to return an instance of FooResult in the sample, but the macro cannot infer it.

So, how do I early return and infer/define the return type in the declarative macros?


Environment

  • rustc 1.55.0
2

There are 2 answers

0
Netwave On BEST ANSWER

return is used in functions not in macros. For the expected behaviour you can wrap the macro into a closure and call it inmediatly:

macro_rules! foo {
    () => {{
        (|| {
        let baz_val = baz();

        let bar_val = match bar() {
            Ok(val) => val,
            Err(err) => {
                return Err(err);
            }
        };

        Ok(Foo(baz_val, bar_val))
        })()

    }};
}

Playground

0
Filipe Rodrigues On

An alternative to @Netwave's answer is to use the nightly-only feature label-break-value as such:

#![feature(label_break_value)]

macro_rules! foo {
    () => {'foo: {
        let baz_val = baz();

        let bar_val = match bar() {
            Ok(val) => val,
            Err(err) => {
                break 'foo Err(err);
            }
        };

        Ok(Foo(baz_val, bar_val))
    }};
}

Playground

I believe this feature was designed explicitly to be able to "return" from arbitrary blocks, which is pretty much your situation.