Changing scope of Try `?`

normally the ? operator works like

fn some_func() -> Result<int, SomeError> {
	let a = foo()?;
	let b = bar(a)?;
	baz(b)
} 

In this case failure in foo() or bar() will exit out of the function.

But I want to handle them inside the function
I could of course do this

fn some_func() -> int {
	match foo() {
		Ok(a) =>
			match bar(a) {
				Ok(b) => baz(b),
				Err(_) => get_default(),
			},
		Err(_) => get_default(),
	}
} 

I know there's nicer ways to do this with ok_or_else or chaining them with and_then but I'm not going to go into those here. What I want to do is something like a new scope for ?

like

fn some_func() -> int {
    let result_of_try =
        try_scope {
            let a = foo()?;
            let b = bar(a)?;
            Ok(baz(b))
        }; 

    match result_of_try {
        Ok(x) => x,
        Err(_) => get_default(),
    }
} 

is there some way I can achieve something like this?

There is an unstable language feature in the works called try block.

1 Like

This seems to be pretty much exactly what I'm looking for.
I wonder why the tracking issue isn't seeing any activity recently. Maybe they're waiting on changes to the try trait itself.

@scottmcm, can you comment on this?

The workaround for now is an IIFE:

fn some_func() -> int {
    let result_of_try =
        (||{
            let a = foo()?;
            let b = bar(a)?;
            Ok(baz(b))
        })(); 

    match result_of_try {
        Ok(x) => x,
        Err(_) => get_default(),
    }
} 

The thing that's currently blocking it is concerns about how it often needs way more type annotations than people would like.

You can find a bunch more discussion of the potential way forward (and a draft RFC) over on zulip: https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/try.20blocks/near/275766941.

6 Likes

I had thought of using a closure like that. But wouldn't that run into issues regarding ownership or moving stuff into the closure? I can't think of an example off the top of my head but I'd guess there would be some limitations, or am I just overthinking this?

Thanks for the link to the discussion, I'll follow it there.

The closure is definitely a workaround. It'll usually work pretty well, but there's a bunch of places where it doesn't. Once the language feature exists, it'll be better.

For example, this doesn't compile: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ae78cf761a6580b11fb183394d3de573

fn some_func() -> u32 {
    let mut x: u32;
    let _result_of_try = (|| {
        x = 2;
        Some(())
    })();
    x
}

error:

error[E0381]: borrow of possibly-uninitialized variable: `x`
 --> src/lib.rs:3:27
  |
3 |     let _result_of_try = (|| {
  |                           ^^ use of possibly-uninitialized `x`
4 |         x = 2;
  |         - borrow occurs due to use in closure

Whereas this works:

#![feature(try_blocks)]
fn some_func() -> u32 {
    let x: u32;
    let _result_of_try: Option<_> = try {
        x = 2;
    };
    x
}

(There are a bunch of examples like this you could probably find related to async.await too, since moving out of closure-based things there had similar advantages.)

Thanks

The blocks I want to shove into the try block are fairly large so I'm pretty sure I'll run into some issue with a closure. Probably best if I just wait for the actual feature or use the experimental one.

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.