RobertN
December 11, 2025, 4:48pm
1
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();
})();
10 Likes
RobertN
December 11, 2025, 5:08pm
3
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.
RobertN:
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();
}
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.)
9 Likes
blonk
December 11, 2025, 6:45pm
6
Would this stabilization include FromResidual? (I know you can't make any promises, but would stabilization of try blocks automatically include stabilization of FromResidual?).
tczajka
December 11, 2025, 6:52pm
7
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); });
2 Likes
The stabilization of the traits is probably separate from the stabilization of try blocks themselves, like how ? is stable without the traits being stable.
1 Like
mroth
December 12, 2025, 12:55am
9
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(...)...
2 Likes
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.