Macro for let else branch

Hello, won’t it be great to support something like that?

fn foo() {
    macro_rules! fallback {
        () => {{
            // bar();
            return;
        }};
    }

    let Some(_) = baz() fallback!();
    let Some(_) = qux() fallback!();
}

Today it gives an error like this:

error: expected `{`, found `fallback`
  --> 
   |
12 |    let Some(_) = baz() else fallback!();
   |                             ^^^^^^^^ expected `{`
   |
help: you might have meant to write this as part of a block
   |
12 |     let Some(_) = baz() else { fallback!() };
   |                              +             +

Why not define a decl macro like this?

macro_rules! let_some {
    ($var:ident = $e:expr) => {
        let Some($var) = $e else { return };
    };
}
fn main() {
    let_some!(var = baz());
    dbg!(var);
}
4 Likes

Here is another one I’ve used a few times. The panic can be replaced with a return of course.

This one has the advantage that it works for any pattern and not just Option<T> [^1]

macro_rules! let_expect {
    ($pat:pat = $value:expr) => {
        let __value = $value;
        let $pat = __value else {
            panic!("Unexpected response: {__value:?}")
        };
    };
}

let_expect!(Some(value) = baz());

[^1]: For Option/Result + Panics you can just use unwrap of course.

…would it?

In C, macros can appear in any position, and are replaced by arbitrary sets of tokens. In Rust, macros do produce tokens, but they’re at least properly bracketed; more interesting is that they cannot appear in any position. A macro in type position should produce tokens that make up a type; a macro in statement position should produce statements, etc.

The position after (or including) the else in a let-else chain isn’t one of these standard locations; you can’t normally put anything there but braces, so it’s not a place macros are looked for in the first place. Could it be? Sure. Is it an improvement? …Not really? The braces are there for a reason, which is to make sure the human and the computer agree on what counts as the else clause. Enforcing that the macro expansion includes braces at compilation time helps the computer, but not really a human reader.

I’ve certainly wanted macro expansion to work in more places than it does, but I’m not sure this is one of them. Even if it’s not quite true, the idea that a macro always expands to a single high-level syntactical item, or a series of them, feels like a good one to me. It makes me not have to think about the macro-ness as much, and what it might do to the structure of the program that I can’t see.

12 Likes

Rust is generally not concerned with allowing people to remove a couple of extra tokens here or there. Just write { fallback!() }; it's fine. And it's much easier for the reader to follow what's going on because they can see the block.

It's a plus that foo!() bar!() baz!() where the foo is the if, the bar is the first block, and the baz having the else doesn't compile.

14 Likes

This pattern is common. I wish postfix macro can help here: playground

fn unwrap_result() {
    let val = 2;
    let expr = Result::<(), _>::Err("??");
    // postfix macro: expr.unwrap!(e, "context val={val}:\n{e}")
    unwrap!(expr, e, "context val={val}:\n{e}");
}
// thread 'unwrap_result' panicked at src/lib.rs:22:5:
// context val=2:
// ??

P.S. I wrote this in a recent discussion.

Just for a bit of context to the let_expect macro:

I’m using it with a custom enum with data, so a .unwrap!(…) wouldn’t help much here.

enum Foo { 
    Bar(u64, u64),
    Baz{ x: u64, y: u64, z: u64 }
}

let_expect!(Foo::Bar(a, _), value1);
let_expect!(Foo::Baz{x,y,z}, value2);

dbg!(a, x, y, z);

// Not sure which is more readable, given that the output variable "a"
// is now after the value itself. This one also might not even work.
value1.expect!(Foo::Bar(a, _));

In practice I’m not really interested in the panic message (this is in unit tests, I just wanted to print the value somewhere when it fails) and more interested in the pattern destructuring itself and keeping the call-site concise => The macro.