Question mark operator in function that returns unit?

In a function body that returns Option<T>, I can use the ? operator either get the Some or early-return None.

fn foo(thing: Thing) -> Option<i32> {
   let x = thing.foo()?.bar()?;
   ...
}

I have some code that defines an event handler function like this one below. The handler closure returns nothing (ie, ()). My typical pattern is to unwrap a few Options.

button.on_click(move |_, c| {
    let Some(controller) = c.upgrade() else { return };
    let Some(nav) = controller.find_navigator() else { return };
    nav.pop();
}

Is there a convenient way to turn that into something like this below?

button.on_click(move |_, c| {
    c.upgrade()?.find_navigator()?.pop()
}

Monadic style:

c.upgrade()
    .and_then(|con| con.find_navigator())
    .and_then(|nav| nav.pop());

Also try blocks, but unfortunately they're still unstable. The best stable replacement is probably an immediately invoked lambda:

(|| {
    c.upgrade()?.find_navigator()?.pop();
})();

Cool, it’s nice to see the improvements in the pipeline.

For my code, that immediately invoked lambda doesn’t type check. It takes it to be a ()-returning function again.

Yeah, you may have to fiddle with it a bit, if the last call in the chain doesn't return an Option anymore, you have to add a dummy Some(()) as the lambda's return value to make it typecheck.

Note that for the easy cases like this you can probably use a chain instead, like

button.on_click(move |_, c| {
    if let Some(controller) = c.upgrade()
        && Some(nav) = controller.find_navigator() 
    {
        nav.pop();
    }
}

But yeah, long-term the answer will be try blocks. (I've been working on those recently to try to get them to stable.)

Would this stabilization include FromResidual? (I know you can't make any promises, but would stabilization of try blocks automatically include stabilization of FromResidual?).

Another solution is to just define a regular function:

fn handle_click(c: ...) -> Option<()> {
    c.upgrade()?.find_navigator()?.pop();
    Some(())
}
    
button.on_click(move |_, c| { handle_click(c); });

The stabilization of the traits is probably separate from the stabilization of try blocks themselves, like how ? is stable without the traits being stable.

Often overlooked, the function could be defined in the closure itself:

button.on_click(move |_, c| {
    fn handle_click(c: ...) -> Option<()> {
        c.upgrade()?.find_navigator()?.pop();
        Some(())
    }
    handle_click(c);
}

This keeps locality, and you can be lazy with function names: fn inner(...)... :wink:

You can use this trick:

macro_rules! try_block_at_home {
    ($($e:tt)*) => {
        (|| std::iter::empty::<std::convert::Infallible>().try_fold({$($e)*}, |_, x| match x {}))()
    }
}

let _: Option<i32> = try_block_at_home! {
    Some(3)?;
    1
};
let _: Option<()> = try_block_at_home! {
    Some(3)?;
};
let _: std::io::Result<_> = try_block_at_home! {
    let v = std::fs::read("asdf.txt")?;
    v.len()
};

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=1400a851e11bf4676df3bf4a3f65cc3a

(Though that's not quite how I currently expect try blocks will work.)

I often use Option<Infallible>, this is equivalent to (). Soon Option<!> should be possible, if not already.

6+ months probably.