Tracking issue for ? operator and try blocks (RFC 243, question_mark & try_blocks features)

7e8790b
Opened by Niko Matsakis at 2025-04-18 23:35:51

Tracking issue for rust-lang/rfcs#243 and rust-lang/rfcs#1859.

Implementation concerns:

  • [x] ? operator that is roughly equivalent to try! - #31954
  • [x] catch { ... } expression - https://github.com/rust-lang/rust/issues/39849
    • [x] resolve do catch { ... } syntax question
      • Resolved as try { .. }, - https://github.com/rust-lang/rust/issues/50412
    • [x] resolve whether catch blocks should "wrap" result value (https://github.com/rust-lang/rust/issues/41414)
  • [x] settle design of the Try trait (https://github.com/rust-lang/rfcs/pull/1859)
    • [x] implement new Try trait (in place of Carrier) and convert ? to use it (https://github.com/rust-lang/rust/pull/42275)
    • [x] add impls for Option and so forth, and a suitable family of tests (https://github.com/rust-lang/rust/pull/42526)
    • [x] improve error messages as described in the RFC (https://github.com/rust-lang/rust/issues/35946)
  • [x] reserve try in new edition
  1. Tracking issue for rust-lang/rfcs#243 and rust-lang/rfcs#1859.

    Implementation concerns:

    • [x] ? operator that is roughly equivalent to try! - #31954
    • [x] try { ... } expression - https://github.com/rust-lang/rust/issues/39849
      • [x] resolve do catch { ... } syntax question
        • Resolved as try { .. }, - https://github.com/rust-lang/rust/issues/50412
      • [x] resolve whether catch blocks should "wrap" result value (first addressed in https://github.com/rust-lang/rust/issues/41414, now being settled anew in https://github.com/rust-lang/rust/issues/70941)
      • [ ] Add a test confirming that it's an ExprWithBlock, so works in a match arm without a comma
      • [ ] Address issues with type inference (try { expr? }? currently requires an explicit type annotation somewhere).
    • [x] settle design of the Try trait (https://github.com/rust-lang/rfcs/pull/1859)
      • [x] implement new Try trait (in place of Carrier) and convert ? to use it (https://github.com/rust-lang/rust/pull/42275)
      • [x] add impls for Option and so forth, and a suitable family of tests (https://github.com/rust-lang/rust/pull/42526)
      • [x] improve error messages as described in the RFC (https://github.com/rust-lang/rust/issues/35946)
    • [x] reserve try in new edition
    • [x] block try{}catch (or other following idents) to leave design space open for the future, and point people to how to do what they want with match instead

    scottmcm at 2018-10-03 16:29:05

  2. The accompanying RFC discusses a desugaring based on labeled return/break, are we getting that too or will there just be special treatment for ? and catch in the compiler?

    EDIT: I think labeled return/break is an excellent idea separate from ? and catch, so if the answer is no I will probably open a separate RFC for it.

    Jonathan Reem at 2016-02-05 20:55:36

  3. Labeled return/break is purely for explanatory purposes.

    On Fri, Feb 5, 2016 at 3:56 PM, Jonathan Reem notifications@github.com wrote:

    The accompanying RFC discusses a desugaring based on labeled return/break, are we getting that too or will there just be special treatment for ? and catch in the compiler?

    — Reply to this email directly or view it on GitHub https://github.com/rust-lang/rust/issues/31436#issuecomment-180551605.

    Niko Matsakis at 2016-02-05 20:58:29

  4. Another unresolved question we have to resolve before stabilizing is what the contract which impls of Into have to obey should be -- or whether Into is even the right trait to use for the error-upcasting here. (Perhaps this should be another checklist item?)

    Gábor Lehel at 2016-02-05 21:20:04

  5. @reem

    I think labeled return/break is an excellent idea ... I will probably open a separate RFC for it.

    Please do!

    Vadim Petrochenkov at 2016-02-05 21:57:12

  6. On the subject of the Carrier trait, here is a gist example of such a trait I wrote back early in the RFC process. https://gist.github.com/thepowersgang/f0de63db1746114266d3

    John Hodge (Mutabah) at 2016-02-06 05:51:05

  7. How this is treated during parsing?

    struct catch {
        a: u8
    }
    
    fn main() {
    
        let b = 10;
        catch { a: b } // struct literal or catch expression with type ascription inside?
    
    }
    

    Vadim Petrochenkov at 2016-02-06 07:55:29

  8. @petrochenkov Well, the definition couldn't affect parsing, but I think we still have a lookahead rule, based on the second token after {, : in this case, so it should still be parsed as a struct literal.

    Eduard-Mihai Burtescu at 2016-02-06 08:00:12

  9. Also

    let c1 = catch { a: 10 };
    let c2 = catch { ..c1 }; // <--
    
    struct catch {}
    let c3 = catch {}; // <--
    

    + https://github.com/rust-lang/rfcs/issues/306 if (when!) implemented. It seems like there are no conflicts besides struct literals.

    Given the examples above I'm for the simplest solution (as usual) - always treat catch { in expression positions as start of a catch block. Nobody calls their structures catch anyway.

    Vadim Petrochenkov at 2016-02-06 08:32:16

  10. It would be easier if a keyword was used instead of catch.

    Alex Burka at 2016-02-06 18:39:06

  11. This is the keywords list: http://doc.rust-lang.org/nightly/grammar.html#keywords

    bluss at 2016-02-06 18:52:18

  12. @bluss yeah, I admit none of them are great... override seems like the only one that is close. Or we could use do, heh. Or a combination, though I don't see any great ones immediately. do catch?

    Alex Burka at 2016-02-06 19:01:56

  13. do is the only one that seems to be close IMO. A keyword soup with do as prefix is a bit irregular, not similar to any other part of the language? Is while let a keyword soup as well? That one feels ok now, when you are used to it.

    bluss at 2016-02-06 19:21:40

  14. port try! to use ?

    Can't ? be ported to use try! instead? This would allow for the use case where you want to get a Result return path, e.g. when debugging. With try! this is fairly easy, you just override the macro at the beginning of the file (or in lib/main.rs):

    macro_rules! try {
        ($expr:expr) => (match $expr {
            Result::Ok(val) => val,
            Result::Err(err) => {
                panic!("Error occured: {:?}", err)
            }
        })
    }
    

    You will get a panic stack trace starting from the first occurrence of try! in the Result return path. In fact, if you do try!(Err(sth)) if you discover an error instead of return Err(sth), you even get the full stack trace.

    But when debugging foreign libraries written by people who haven't implemented that trick, one relies on try! usage somewhere higher in the chain. And now, if the library uses the ? operator with hardcoded behaviour, getting a stacktrace gets almost impossible.

    It would be cool if overriding try! would affect the ? operator as well.

    Later on when the macro system gets more features you can even panic! only for specific types.

    If this proposal requires an RFC please let me know.

    est31 at 2016-02-07 18:50:02

  15. Ideally ? could just be extended to provide stack trace support directly, rather than relying on the ability to override try!. Then it would work everywhere.

    Russell Johnston at 2016-02-07 21:14:19

  16. Stack traces are just one example (though a very useful one, seems to me). If the Carrier trait is made to work, maybe that can cover such extensions?

    On Sun, Feb 7, 2016 at 4:14 PM, Russell Johnston notifications@github.com wrote:

    Ideally ? could just be extended to provide stack trace support directly, rather than relying on the ability to override try!. Then it would work everywhere.

    — Reply to this email directly or view it on GitHub https://github.com/rust-lang/rust/issues/31436#issuecomment-181118499.

    Alex Burka at 2016-02-07 21:16:42

  17. Without wanting to speculate, I think that it could work, albeit with some issues.

    Consider the usual case where one has code returning some Result<V,E> value. Now we would need to allow for multiple implementations of the Carrier trait to coexist. In order to not run into E0119, one has to make all implementations out of scope (possibly through different traits which are per default not imported), and when using the ? operator, the user is required to import the wished implementation.

    This would require everybody, even those who don't want to debug, to import their wished trait implementation when using ?, there would be no option for a predefined default.

    Possibly E0117 can be an issue too if wanting to do custom Carrier implementations for Result<V,E>, where all types are outside bounds, so libstd should provide already provide a set of implementations of the Carrier trait with the most used use cases (trivial implementation, and panic! ing implementation, perhaps more).

    Having the possibility to override via a macro would provide a greater flexibility without the additional burden on the original implementor (they don't have to import their wished implementation). But I also see that rust never had a macro based operator before, and that implementing ? via a macro isn't possible if catch { ... } is supposed to work, at least not without additional language items (return_to_catch, throw, labeled break with param as used in RFC 243).

    I am okay with any setup which enables one to get Result stacktraces with an Err return path, while having only to modify a very small amount of code, prefferably at the top of the file. The solution should also work unrelated to how and where the Err type is implemented.

    est31 at 2016-02-08 00:23:45

  18. Just to chime in on bikeshedding: catch in { ... } flows pretty nicely.

    robert at 2016-02-08 02:14:23

  19. catch! { ... } is another backcompat choice.

    Alex Burka at 2016-02-08 18:57:25

  20. Also, not that I expect this to change anything, but a note that this is going to break multi-arm macros that were accepting $i:ident ?, in the same way that type ascription broke $i:ident : $t:ty.

    Alex Burka at 2016-02-08 19:56:41

  21. Don't overdo the backwards compatibility, just treat catch as a keyword when followed by { (possibly only in expression position, but I'm not sure if that changes much compatibility-wise).

    I can also imagine some possible problems that don't involve struct literals (e.g. let catch = true; if catch {}); but I prefer a minor breaking change over a more ugly syntax.

    Didn't we have a for adding new keywords, anyways? We could offer some kind of from __future__ opt-in for new syntax; or specify a rust language version number on the command-line / in Cargo.toml. I highly doubt that in the long term, we'll be able to work with only those keywords that are already reserved. We don't want our keywords to have three different meanings each, depending on context.

    Daniel Grunwald at 2016-02-08 20:22:38

  22. I agree. This isn't even the first RFC where this has come up (https://github.com/rust-lang/rfcs/pull/1444 is another example). I expect it won't be the last. (Also default from https://github.com/rust-lang/rfcs/pull/1210, although it's not an RFC I'm in favor of.) I think we need to find a way to add honest-to-god keywords instead of trying to figure out how to ad-hoc hack the grammar for every new case.

    Gábor Lehel at 2016-02-09 09:23:17

  23. Wasn't the whole argument for not reserving several keywords prior to 1.0 that we'd definitely be introducing a way to add new keywords to the language backward compatibly (possibly by explicitly opting in), so there was no point? Seems like now would be a good time.

    Erik Jensen at 2016-02-26 02:26:03

  24. @japaric Are you interested in reviving your old PR and taking this on?

    Aaron Turon at 2016-02-26 03:22:22

  25. @aturon My implementation simply desugared foo? in the same way as try!(foo). It also only worked on method and function calls, i.e. foo.bar()? and baz()? work but quux? and (quux)? don't. Would that be okay for an initial implementation?

    Jorge Aparicio at 2016-02-26 21:08:47

  26. @japaric What was the reason for restricting it to methods and function calls? Wouldn't parsing it be easier as a general postfix operator?

    Eduard-Mihai Burtescu at 2016-02-26 21:12:17

  27. What was the reason for restricting it to methods and function calls?

    easiest way (for me) to test the ? expansion

    Wouldn't parsing it be easier as a general postfix operator?

    probably

    Jorge Aparicio at 2016-02-26 21:26:24

  28. @japaric It'd probably be good to generalize it to a full postfix operator (as @eddyb is suggesting), but it's fine to land ? with the simple desugaring and then add catch later.

    Aaron Turon at 2016-02-26 21:31:46

  29. @aturon Alright, I'll look into the postfix version by next week if no one beats me to it :-).

    Jorge Aparicio at 2016-02-26 22:01:28

  30. rebased/updated my PR in #31954 :-)

    Jorge Aparicio at 2016-02-28 23:28:58

  31. What about support for providing stack traces? Is that planned?

    est31 at 2016-02-29 03:15:00

  32. I hate to be the +1 guy, but stack traces have saved good chunks of my time in the past. Maybe on debug builds, and when hitting the error path, the ? operator could append the file/line to a Vec in Result? Maybe the Vec could be debug-only too?

    And that could be either exposed or turned into part of the error description...

    Gustavo Hexsel at 2016-02-29 21:49:37

  33. I keep running into the situation where I want to use try! / ? inside iterators returning Option<Result<T, E>>. Unfortunately that currently does not really work. I wonder if the carrier trait could be overloaded to support this or would that go into a more generic From instead?

    Armin Ronacher at 2016-03-12 22:07:49

  34. @hexsel I really wish the Result<> type would carry a vec of instruction pointers in debug and ? would append to it. That way DWARF info could be used to build a readable stacktrace.

    Armin Ronacher at 2016-03-12 23:57:43

  35. @mitsuhiko But how could you create and pattern-match Result? It's just an enum atm.

    As for the Option wrapping, I believe you want Some(catch {...}).

    Eduard-Mihai Burtescu at 2016-03-13 00:28:07

  36. Currently, my habit right now is to do try!(Err(bla)) instead of return Err(), so that I can override the try macro later on with one that panics, in order to get a backtrace. It works well for me, but the code I deal with is very low level, and mostly originates the errors. I still will have to avoid ? if I use external code that returns Result.

    est31 at 2016-03-13 01:13:23

  37. @eddyb it would need language support to carry hidden values in addition that you need to manipulate by other means. I was wondering if it can be done in other ways but I can't see how. The only other way would have been a standardized error box that can have additional data on it, but there is no requirement to have boxed errors and most people don't do it.

    Armin Ronacher at 2016-03-13 08:45:50

  38. @mitsuhiko I can think of a new (default) method on the Error trait and TLS. The latter is used by sanitizers sometimes.

    Eduard-Mihai Burtescu at 2016-03-13 08:55:04

  39. @eddyb that only works though if the Result can be identified and that requires it to be boxed or it will move around in memory as it passes upwards the stack.

    Armin Ronacher at 2016-03-13 14:37:28

  40. @mitsuhiko The TLS? Not really, you just need to be able to compare the error by-value.

    Eduard-Mihai Burtescu at 2016-03-13 14:39:07

  41. Or even just by type (with linking From inputs and outputs), how many concurrent errors you want stacktraces from will ever have to be propagated simultaneously?

    I am against adding Result-specific compiler hacks, personally, when simpler solutions work.

    Eduard-Mihai Burtescu at 2016-03-13 14:40:10

  42. @eddyb the error passes upwards the stack. What you want is the EIP at every stack level, not just where it originates. Also errors are a) currently not comparable and b) just because they compare equal does not mean they are the same error.

    how many concurrent errors you want stacktraces from will ever have to be propagated simultaneously

    Any error caught down and rethrown as different error.

    I am against adding Result-specific compiler hacks, personally, when simpler solutions work.

    I don't see how a simpler solution works but maybe I'm missing something there.

    Armin Ronacher at 2016-03-13 14:48:25

  43. You can save the instruction pointer at every ? and correlate it with the error type. "Any error caught down and rethrown as different error." But how would you preserve that information if it was hidden in Result?

    Eduard-Mihai Burtescu at 2016-03-13 14:50:55

  44. But how would you preserve that information if it was hidden in Result?

    You don't need to store that information in the result. What you do need to store though is a unique ID for the origin of the failure so you can correlate it. And because the error trait is just a trait and does not have storage, it could be stored in the result instead. The instruction pointer vec itself would by no means have to be stored in result, that could go to TLS.

    One way would be that you invoke a method failure_id(&self) on the result and it returns an i64/uuid or something that identifies the origin of the failure.

    This would need language support no matter what because what you need is that as the result passes upwards through the stack, the compiler injects an instruction to record the stack frame it falls through. So the return of a result would look different in debug builds.

    Armin Ronacher at 2016-03-13 14:55:06

  45. "the compiler injects an instruction to record the stack frame it falls through" - but ? is explicit, this is nothing like exceptions, or do you not like recording only the ? it passed through?

    Anyway, if you manually unpack the error and then put it back in an Err, how would that ID even be kept?

    Eduard-Mihai Burtescu at 2016-03-13 14:57:39

  46. "And because the error trait is just a trait and does not have storage, it could be stored in the result instead" There is a variant on this: implementing the Error trait could be special-cased in the compiler to add an extra integer field to the type, creating the type would trigger an ID to be generated, ~~and copy/drop would effectively increment/decrement the refcount (and eventually clear it from the TLS "in-flight error set" if Result::unwrap is not used).~~

    But that would conflict with the Copy trait. I mean, so would your plan, adding any special behavior to Result that is not triggered by ? or other specific user actions can invalidate the Copy invariants.

    ~~EDIT: At this point you might as well embed an Rc<ErrorTrace> in there.~~ ~~EDIT2: What am I even saying, you can clear the associated error trace on catch.~~ EDIT3: Actually, on drop, see below a better explanation.

    Eduard-Mihai Burtescu at 2016-03-13 15:09:28

  47. "the compiler injects an instruction to record the stack frame it falls through" - but ? is explicit, this is nothing like exceptions, or do you not like recording only the ? it passed through?

    That does not work because there are too many frames you can fall through which do not use ?. Let alone that not everybody is going to handle errors with just ?.

    Anyway, if you manually unpack the error and then put it back in an Err, how would that ID be even kept?

    That's why it would have to be compiler support. The compiler would have to track local variables that are results and do it's best to propagate the result id onwards for re-wraps. If this is too magical then it could be restricted to a subset of operations.

    Armin Ronacher at 2016-03-13 15:15:57

  48. That does not work because there are too many frames you can fall through which do not use ?. Let alone that not everybody is going to handle errors with just ?.

    Okay, I could see returning Result directly be special-cased in complex functions with multiple return paths (some of which would be early returns from ?).

    If this is too magical then it could be restricted to a subset of operations.

    Or made entirely explicit. Do you have examples of non-? re-wrapping that would have to be magically tracked by the compiler?

    Eduard-Mihai Burtescu at 2016-03-13 15:19:27

  49. @eddyb The common case of manual handling of errors is an IoError where you want to handle a subset:

    loop {
      match establish_connection() {
        Ok(conn) => { ... },
        Err(err) => {
          if err.kind() == io::ErrorKind::ConnectionRefused {
            continue;
          } else {
            return Err(err);
          }
        }
      }
    }
    

    Armin Ronacher at 2016-03-13 15:23:53

  50. @mitsuhiko Then keeping the ID inside io::Error would definitely work.

    Eduard-Mihai Burtescu at 2016-03-13 15:51:44

  51. So to recapitulate, a Vec<Option<Trace>> "sparse integer map" in TLS, keyed by struct ErrorId(usize) and accessed by 3 lang items:

    • fn trace_new(Location) -> ErrorId, when creating a non-const error value
    • fn trace_return(ErrorId, Location), just before returning from a function declared as -> Result<...> (i.e. not a function generic on the return that that happens to be used with a Result type there)
    • fn trace_destroy(ErrorId), when dropping an error value

    If the transformation is done on MIR, Location can be computed from the Span of the instruction triggering either construction of an error value or writing to Lvalue::Return, which is much more reliable than an instruction pointer IMO (no easy way to get that in LLVM anyway, you'd have to emit inline asm for each specific platform).

    Eduard-Mihai Burtescu at 2016-03-13 16:11:49

  52. @eddyb

    Won't that lead to binary-size bloat?

    Ariel Ben-Yehuda at 2016-03-13 17:25:02

  53. @arielb1 You'd do it in debug mode only where the debuginfo bloats the binary anyway - you could also reuse the debuginfo cleverly shrug.

    Eduard-Mihai Burtescu at 2016-03-13 17:29:38

  54. @eddyb what is a Location in that case? Not sure what's hard about reading the IP is. Sure, you need custom JS for each target but that's not really all that hard.

    Armin Ronacher at 2016-03-14 09:58:34

  55. @mitsuhiko it could be the same info we use for overflow checks and other compiler-emitted panics. See core::panicking::panic.

    Eduard-Mihai Burtescu at 2016-03-14 13:40:07

  56. Why pack so much stack information to an Error/Result while the stack is unwound? Why not just run the code while the stack is still there? E.g. what if you are interested in variables on the path of the stack? Just enable people to run custom code when an Err gets invoked, e.g. to call up a debugger of their choice. This is what try! already provides due to being an (overrideable) macro. The easiest way is to panic on an Err case which prints the stack provided one has started the program with the right params.

    With try you can do anything you want on an Err case, and you can override the macro crate wide, or very scoped to not touch performance critical code e.g. if the error is hard to reproduce and you need to run alot of the perf critical code.

    Nobody would need to preserve a fraction of the information in an artificial stack one builds up because the real one is going to be destroyed. All the macro overriding method could be improved with is:

    • ? being overridable in a similar way, easiest way would be by defining ? as sugar for try! -- especially needed in order to catch non originating errors at the crate border as outlined in my comment above.
    • having a more powerful macro system, like matching on the type in order to allow for even more flexibility. Yes, one could think about putting this into the traditional trait system, but rust allows no overriding of implementations of traits, so that's going to be a bit complicated

    est31 at 2016-03-14 13:48:55

  57. @est31 The stack is not automatically "unwound" like in a panic, this is syntactical sugar for early returns. And yes, you could have the compiler insert calls of some empty functions with fixed names that you can breakpoint on, this is pretty easy (and also have information that lets you do, e.g. call dump(data) - where dump and data are arguments debuggers can see).

    Eduard-Mihai Burtescu at 2016-03-14 14:03:54

  58. @eddyb I think empty functions would not allow a use-case of, e.g. keeping a few "canary" debug instances on a large deployment to see if errors appear in logs, to then go back and fix things. I understand it is preferrable to be proactive than reactive, but not everything is easy to predict

    Gustavo Hexsel at 2016-03-14 14:19:14

  59. @hexsel Yeah, which is why I prefer the TLS-based method where Result::unwrap or some new ignore method (or even always when dropping the error?) dumps the trace to stderr.

    Eduard-Mihai Burtescu at 2016-03-14 14:21:59

  60. @eddyb If you add the instruction pointer or something derived from the value to a stack like data structure in TLS then you basically rebuild your small version of the real life stack. A return reduces the stack by one entry. So if you do that while you return you unwind part of the stack, while building a limited version of it at some other place in RAM. Perhaps "unwind" is the wrong term for behaviour resulting from "legal" returns, but if all code does ? or try! and at the end you intercept the end result is the same. Its great that rust does not make error propagation automatic, I really liked how java had required all exceptions to be listed after the throws keyword, rust is a good improvement on that.

    @hexsel an overriding (as it already exists now for try!) based approach would allow for this -- you can run any code you wish, and log to any logging system. You would need some detection for "dupes" when multiple try's catch the same error as it propagates up the stack though.

    est31 at 2016-03-14 14:26:48

  61. @est31 Overriding try! only works in your own code (it's literally macro import shadowing), it would have to be something different, like our "weak lang items".

    @est31 That's not really correct (about unwinding), the traces and the real stack don't necessarily have any relationship, because moving Results around doesn't have to go up in the original backtrace, it can go sideways too. Also, if the binary is optimized, most of the backtrace is gone, and if you don't have debug info, then Location is strictly superior. You could even be running an obfuscating plugin which replaces all the source information with random hashes that can be matched by the developers of some closed source product.

    Debuggers are useful (and as an aside, I'm loving lldb's cleaner backtrace output), but they're not a panacea, and we already output some information on panics so you can get some clues as to what's going on.

    About that - I had some thoughts about a typesystem-level trick where {Option,Result}::unwrap would have an extra type argument, defaulting to a type dependent on the location the function was called from, such that the panics from those would have way more useful location information.

    With progress on value parametrization, that might still be an option, but definitely not the only one, and I don't want to outright dismiss Result traces, instead I'm trying to find a model which is implementable.

    Eduard-Mihai Burtescu at 2016-03-14 14:36:37

  62. Overriding try! is not a solution at all because it's contained within your own crate. That's unacceptable as debugging experience. I already tried plenty of that trying to deal with the current try!. Especially if you have many crates involved and errors get transmuted multiple times on the way through the stack it's nearly impossible to figure out where the error originates and why. If a bandaid like that is the solution then we will have to revisit error handling in general for large Rust projects.

    @eddyb so is your suggestion that we literally embed the filename, function-name, line number and column number to that vector? That seems hugely wasteful particularly because that information is already contained in much more processable format in DWARF. Also DWARF lets us use the same process reasonably cheaply in production whereas this sort of location info appears to be so wasteful that nobody would ever run a release binary with it.

    Armin Ronacher at 2016-03-14 15:06:53

  63. How would it be significantly more wasteful than DWARF information? Filenames would be deduplicated, and on x64 the whole thing is the size of 3 pointers.

    Eduard-Mihai Burtescu at 2016-03-14 15:12:20

  64. @mitsuhiko so basically, you agree with the general direction, but not with the technical specifics of it?

    How easy is it to expose DWARF information to a general Rust API?

    Gustavo Hexsel at 2016-03-14 15:12:50

  65. @eddyb because DWARF information is not contained within the release binary but separate files. So I can have the debug files on symbols servers and ship a stripped binary to users.

    Armin Ronacher at 2016-03-14 15:13:54

  66. @mitsuhiko Oh, that's an entirely different approach than what I was assuming. Rust doesn't currently support that atm, AFAIK, but I agree it should.

    Do you think instruction pointers are actually that useful compared to random identifiers generated by your build system for the purposes of debugging release binaries?

    My experience has been that with any inlining debuggers have a hard time recovering much of the stack trace, except for explicit self/mutual recursion and very large functions.

    Eduard-Mihai Burtescu at 2016-03-14 15:17:42

  67. Yes, try! is contained within your crate. If you pass a function pointer or similar to library code, and there is an error in your function, the try approach will still work. If a crate you use has an internal error or bug, you might need its source code in order to debug already, if the returned Err's information does not help. Stack traces are only helpful if you have access to the code, so closed source libraries (whose code you can't modify) will go have to go over support one way or another.

    What about simply enabling both approaches, and let the developer decide what's best for them? I do not deny that the TLS based approach doesn't have any advantages.

    The try model is implementable very easily, just desugar ? to try, only extra language extensions are needed if catch comes in (you would have added that behaviour inside the hardcoded behaviour of ? anyway)

    est31 at 2016-03-14 15:20:54

  68. @eddyb "Do you think instruction pointers are actually that useful compared to random identifiers generated by your build system for the purposes of debugging release binaries?"

    That's how debugging native binaries in general works. We (Sentry) are using that almost entirely for the iOS Support at this point. We get a crash dump and resolve the addresses with llvm-symbolizer to the actual symbols.

    Armin Ronacher at 2016-03-14 15:21:13

  69. @mitsuhiko Since we already embed libbacktrace, we could use that to resolve code pointers to source locations, so I'm not entirely opposed to it.

    Eduard-Mihai Burtescu at 2016-03-14 15:24:09

  70. @eddyb yeah. Just looked at the panic code. Would be nice if that was actually an API that Rust code could use. I can see this being useful in a few more places.

    Armin Ronacher at 2016-03-14 15:26:11

  71. About that - I had some thoughts about a typesystem-level trick where {Option,Result}::unwrap would have an extra type argument, defaulting to a type dependent on the location the function was called from, such that the panics from those would have way more useful location information.

    Speaking of which...

    Gábor Lehel at 2016-03-14 23:36:32

  72. @glaebhoerl Hah, maybe that's actually worth pursuing then? At least as some unstable experiment.

    Eduard-Mihai Burtescu at 2016-03-15 04:28:46

  73. @eddyb No idea. Probably worth discussing it with some GHCers involved with it first, I only heard about this in passing and googled a link just now. And Rust doesn't have actual implicit parameters in the way that GHC does; whether default type parameters could work instead is an interesting question (probably one you've given more thought to than I).

    Gábor Lehel at 2016-03-15 12:55:33

  74. Just a thought: it'd be useful to have a switch that causes rustc to special-case constructing an Err such that it calls a fn(TypeId, *mut ()) function with the payload before returning. That should be enough to start with basic stuff like filtering errors based on the payload, trapping into a debugger if it sees an error of interest, or capturing backtraces for certain kinds of errors.

    Daniel Keep at 2016-03-16 06:19:34

  75. PR 33389 adds experimental support for the Carrier trait. Since it wasn't part of the original RFC, it should get a particularly close period of examination and discussion before we move to FCP (which should probably be separate to the FCP for the rest of the ? operator). See this discuss thread for more details.

    Nick Cameron at 2016-05-18 02:27:27

  76. I'm opposed to extending ? to Options.

    The wording of the RFC is pretty clear about the fact that the ? operator is about propagating errors/"exceptions". Using Option to report an error is the wrong tool. Returning None is part of the normal workflow of a successful program, while returning Err always indicates an error.

    If we ever want to improve some areas of errors handling (for example by adding stack traces), implementing ? on Option means that we will have to exclude ? from the changes.

    Pierre Krieger at 2016-05-18 05:48:12

  77. @tomaka could we keep the discussion on the discuss thread? (I've summarized your objection already) I personally find that long discussions on GH get pretty unwieldy, and it'd also be nice to be able to separate discussion this particular point from other future points or questions that may arise.

    Niko Matsakis at 2016-05-18 11:46:04

  78. @eddyb Here's the released-version docs of the implicit callstacks GHC feature I mentioned earlier.

    Gábor Lehel at 2016-05-21 18:56:30

  79. No updates here in a while. Is anybody working to move this forward? Are the remaining tasks in the OP still accurate? Can anything here be E-help-wanted?

    Brian Anderson at 2016-07-16 18:35:56

  80. I played around last weekend if it would be possible to write a Sentry client for Rust that makes any sense. Since most error handling is actually based on results now instead of panics I noticed that the usefulness of this is severely limited to the point where I just decided to abandon this entirely.

    I went to the crates.io codebase as an example to try to integrate an error reporting system into it. This brought me back to this RFC because I really think that unless we can record the instruction pointer somehow as results are passed down and converted in the different stacktraces it will be impossible to get proper error reporting. I already see this being a massive pain just debugging local complex logic failures where I nowadays resort to adding panics where I think the error is coming from.

    Unfortunately I currently do not see how the IP could be recorded without massive changes to how results work. Has anyone else played around with this idea before?

    Armin Ronacher at 2016-07-21 06:23:44

  81. We were discussing this in the @rust-lang/lang meeting. Some things that came up:

    First, there is definite interest in seeing ? stabilizing as quickly as possible. However, I think most of us would also like to see ? operating on Option and not just Result (but not, I think, bool, as has also been proposed). One concern about stabilizing is that if we stabilize without offering any kind of trait that would permit types other than Result to be used, then it is not backwards compatible to add it later.

    For example, I myself write code like this with some regularity:

    let v: Vec<_> = try!(foo.iter().map(|x| x.to_result()).collect());
    

    where I am relying on try! to inform type inference that I expect collect to return a Result<Vec<_>, _>. If were to use ?, this inference might fail in the future.

    However, in previous discussions we also decided that an amendment RFC was needed to discuss the finer points of any sort of "carrier" trait. Clearly this RFC should be written ASAP, but we'd prefer not to block progress on ? on that discussion.

    One thought we had was that if we took @nrc's implementation and we made the trait unstable and implemented it only for Result and some private dummy type, that ought to suppress inference while still only making ? usable with Result.

    One final point: I think most of us would prefer that if you use ? on an Option value, it requires that your function's return type must also be Option (not e.g. Result<T,()>). It's interesting to note that this will help with the inference limitations, since we can ultimately infer from the declared return type in many cases what type is required.

    The reason for not wanting inter-conversion is that it seems likely to lead to loose logic, sort of analogous to how C permits if x even when x has integral type. That is, if Option<T> denotes a value where None is part of the domain of that value (as it should), and Result<> represents (typically) the failure of a function to succeed, then assuming that None means the function should error out seems suspect (and like a kind of arbitrary convention). But that discussion can wait for the RFC, I suppose.

    Niko Matsakis at 2016-07-22 20:50:12

  82. The reason for not wanting inter-conversion is that it seems likely to lead to loose logic, sort of analogous to how C permits if x even when x has integral type. That is, if Option<T> denotes a value where None is part of the domain of that value (as it should), and Result<> represents (typically) the failure of a function to succeed, then assuming that None means the function should error out seems suspect (and like a kind of arbitrary convention). But that discussion can wait for the RFC, I suppose.

    I heartily agree with this.

    Erik Jensen at 2016-07-22 22:04:05

  83. Another question we had agreed to gate stabilization on was nailing down the contracts that impls of the From trait should obey (or whatever trait we wind up using for Err-upcasting).

    Gábor Lehel at 2016-07-23 13:50:16

  84. @glaebhoerl

    Another question we had agreed to gate stabilization on was nailing down the contracts that impls of the From trait should obey (or whatever trait we wind up using for Err-upcasting).

    Indeed. Can you refresh my memory and start out with some examples of things you would think should be forbidden? Or perhaps just the laws you have in mind? I have to admit that I am wary of "laws" like this. For one thing, they have a tendency to get ignored in practice -- people take advantage of the actual behavior when it suits their purposes, even if it goes beyond the intended limitations. So that leads to one other question: if we had laws, would we use them for anything? Optimizations? (Seems unlikely to me, though.)

    Niko Matsakis at 2016-07-26 13:34:42

  85. By the way, what is the state of catch expressions? Are they implemeted?

    Alexander Bulaev at 2016-07-26 13:41:12

  86. Sadly no :(

    On Tue, Jul 26, 2016 at 06:41:44AM -0700, Alexander Bulaev wrote:

    By the way, what is the state of catch expressions? Are they implemeted?


    You are receiving this because you authored the thread. Reply to this email directly or view it on GitHub: https://github.com/rust-lang/rust/issues/31436#issuecomment-235270663

    Niko Matsakis at 2016-07-26 15:48:16

  87. Perhaps you should plan implementing it? There is nothing more depressing than accepted but not implemented RFCs...

    Alexander Bulaev at 2016-07-26 15:54:59

  88. cc #35056

    Nick Cameron at 2016-07-28 21:30:26

  89. FYI, https://github.com/rust-lang/rfcs/pull/1450 (types for enum variants) would open up some interesting ways implement Carrier. For example, something like:

    trait Carrier {
        type Success: CarrierSuccess;
        type Error: CarrierError;
    }
    
    trait CarrierSuccess {
        type Value;
        fn into_value(self) -> Self::Value;
    }
    
    // (could really use some HKT...)
    trait CarrierError<Equivalent: CarrierError> {
        fn convert_error(self) -> Equivalent;
    }
    
    impl<T, E> Carrier for Result<T, E>
    {
        type Success = Result::Ok<T, E>;
        type Error = Result::Err<T, E>;
    }
    
    impl<T, E> CarrierSuccess for Result::Ok<T, E> {
        type Value = T;
        fn into_value(self) -> Self::Value {
            self.0
        }
    }
    
    impl<T, E1, E2> CarrierError<Result::Err<T, E2>> for Result::Err<T, E1>
        where E2: From<E1>,
    {
        fn convert_error(self) -> Result::Err<T, E2> {
            Err(self.into())
        }
    }
    
    impl<T> Carrier for Option<T>
    {
        type Success = Option::Some<T>;
        type Error = None;
    }
    
    impl<T> CarrierSuccess for Option::Some<T> {
        type Value = T;
        fn into_value(self) -> Self::Value {
            self.0
        }
    }
    
    impl<T> CarrierError<Option::None> for Option::None {
        fn convert_error(self) -> Option::None {
            self
        }
    }
    
    fn main() {
        let value = match might_be_err() {
            ok @ Carrier::Success => ok.into_value(),
            err @ Carrier::Error => return err.convert_error(),
        }
    }
    

    Steven Allen at 2016-08-09 15:41:39

  90. I just wanted to cross-post some thoughts from https://github.com/rust-lang/rust/pull/35056#issuecomment-240129923. That PR introduces a Carrier trait with dummy type. The intention was to safeguard -- in particular, we wanted to stabilize ? without having to stabilize its interaction with type inference. This combination of trait plus dummy type seemed like it would be safely conservative.

    The idea was (I think) that we would then write-up an RFC discussing Carrier and try to modify the design to match, stabilizing only when we were happy with the overall shape (or possibly removing Carrier altogether, if we can't reach a design we like).

    Now, speaking a bit more speculatively, I anticipate that, if we do adopt a Carrier trait, we would want to disallow interconversion between carriers (whereas this trait is basically a way to convert to/from Result). So intuitively if you apply ? to an Option, that's ok if the fn returns Option; and if you apply ? to a Result<T,E>, that's ok if the fn returns Result<U,F> where E: Into<F>; but if you apply ? to an Option and the fn returns Result<..>, that's not ok.

    That said, this sort of rule is hard to express in today's type system. The most obvious starting point would be something like HKT (which of course we don't really have, but let's ignore that for now). However, that's not obviously perfect. If we were to use it, one would assume that the Self parameter for Carrier has kind type -> type -> type, since Result can implement Carrier. That would allow us to express things like Self<T,E> -> Self<U,F>. However, it would not necessarily apply to Option, which has kind type -> type (all of this would of course depend on precisely what sort of HKT system we adopted, but I don't think we'll go all the way to "general type lambdas"). Even more extreme might be a type like bool (although I don't want to implement Carrier for bool, I would expect some people might want to implement Carrier for a newtype'd bool).

    What I had considered is that the typing rules for ? could themselves be special: for example, we might say that ? can only be applied to a nominal type Foo<..> of some kind, and that it will match the Carrier trait against this type, but it will require that the return type of the enclosing fn is also Foo<..>. So we would basically instantiate Foo with fresh type parameters. The downside of this idea is that if neither the type that ? is being applied to nor the type of the enclosing fn is known, we can't enforce this constraint without adding some new kind of trait obligation. It's also rather ad-hoc. :) But it would work.

    Another thought I had is that we might reconsider the Carrier trait. The idea would be to have Expr: Carrier<Return> where Expr is the type ? is applied to and Return is the type of the environment. For example, perhaps it might look like this:

    trait Carrier<Target> {
        type Ok;
        fn is_ok(&self) -> bool; // if true, represents the "ok-like" variant
        fn unwrap_into_ok(self) -> Self::Ok; // may panic if not ok
        fn unwrap_into_error(self) -> Target; // may panic if not error
    }
    

    Then expr? desugars to:

    let val = expr;
    if Carrier::is_ok(&val) {
        val.unwrap_into_ok()
    } else {
        return val.unwrap_into_error();
    }
    

    The key difference here is that Target would not be the error type, but a new Result type. So for example we might add the following impl:

    impl<T,U,E,F> Carrier<Result<U,F>> for Result<T,E>
        where E: Into<F>
    {
        type Ok = T;
        fn is_ok(&self) -> bool { self.is_ok() }
        fn unwrap_into_ok(self) -> Self::Ok { self.unwrap() }
        fn unwrap_into_error(self) -> { Err(F::from(self.unwrap_err())) }
    }
    

    And then we might add:

    impl<T> Carrier<Option<T>> for Option<T> {
        type Ok = T;
        fn is_ok(&self) -> bool { self.is_some() }
        fn unwrap_into_ok(self) -> Self::Ok { self.unwrap() }
        fn unwrap_into_error(self) -> { debug_assert!(self.is_none()); None }
    }
    

    And finally we could implement for bool like so:

    struct MyBool(bool);
    impl<T> Carrier<MyBool> for MyBool {
        type Ok = ();
        fn is_ok(&self) -> bool { self.0 }
        fn unwrap_into_ok(self) -> Self::Ok { debug_assert!(self.0); () }
        fn unwrap_into_error(self) -> { debug_assert!(!self.0); self }
    }
    

    Now this version is more flexible. For example, we could allow interconversion between Option values to be converted to Result by adding an impl like:

    impl<T> Carrier<Result<T,()>> for Option<T> { ... }
    

    But of course we don't have to (and we wouldn't).

    Niko Matsakis at 2016-08-16 15:04:38

  91. @Stebalien

    FYI, rust-lang/rfcs#1450 (types for enum variants) would open up some interesting ways implement Carrier

    As I was writing up that idea I just wrote, I was thinking about having types for enum variants, and how that might affect things.

    One thing I noticed writing some code that uses ? is that it is mildly annoying not to have any kind of "throw" keyword -- in particular, if you write Err(foo)?, the compiler doesn't know that this will return, so you have to write return Err(foo). That's ok, but then you don't get the into() conversions without writing them yourself.

    This comes up in cases like:

    let value = if something_or_other() { foo } else { return Err(bar) };
    

    Niko Matsakis at 2016-08-16 15:07:47

  92. Oh, I should add one other thing. The fact that we allow impls to influence type inference should mean that foo.iter().map().collect()?, in a context where the fn returns a Result<..>, I suspect no type annotations would be required, since if we know that the fn return type is Result, only one impl would potentially apply (locally, at least).

    Niko Matsakis at 2016-08-16 15:09:30

  93. Oh, and, a slightly better version of my Carrier trait would probably be:

    trait Carrier<Target> {
        type Ok;
        fn into_carrier(self) -> Result<Self::Ok, Target>;
    }
    

    where you would implement it like:

    impl<T,U,E,F> Carrier<Result<U,F>> for Result<T,E>
        where E: Into<F>
    {
        type Ok = T;
        fn into_carrier(self) -> Result<T, Result<U,F>> {
            match self { Ok(v) => Ok(v), Err(e) => Err(e.into()) }
        }
    }
    

    And expr? would generate code like:

    match Carrier::into_carrier(expr) {
        Ok(v) => v,
        Err(e) => return e,
    }
    

    A downside (or upside...) of this of course is that the Into conversions are pushed into the impls, which means that people might not use them when it makes sense. But also means you can disable them if (for your particular type) that are not desired.

    Niko Matsakis at 2016-08-16 15:18:50

  94. @nikomatsakis IMO, the trait should be IntoCarrier and IntoCarrier::into_carrier should return a Carrier (a new enum) instead of re-using result like this. That is:

    enum Carrier<C, R> {
        Continue(C),
        Return(R),
    }
    trait IntoCarrier<Return> {
        type Continue;
        fn into_carrier(self) -> Carrier<Self::Continue, Return>;
    }
    

    Steven Allen at 2016-08-16 16:08:28

  95. @Stebalien sure, seems fine.

    Niko Matsakis at 2016-08-17 15:45:46

  96. Nominating for discussion (and possible FCP of the ? operator alone) at the lang team meeting. I assume we need to land some kind of temporary Carrier trait in the next few days to FCP.

    Nick Cameron at 2016-08-17 22:50:43

  97. I opened rust-lang/rfcs#1718 to discuss the Carrier trait.

    Nick Cameron at 2016-08-18 04:18:16

  98. Hear ye, hear ye! The ? operator specifically is now entering final comment period. This discussion lasts for roughly this release cycle which began on August 18th. The inclination is to stabilize the ? operator.

    With respect to the carrier trait, a temporary version landed in #35777 which should ensure that we have the freedom to decide either way by preventing undesired interaction with type inference.

    @rust-lang/lang members, please check off your name to signal agreement. Leave a comment with concerns or objections. Others, please leave comments. Thanks!

    • [x] @nikomatsakis
    • [x] @nrc
    • [x] @aturon
    • [x] @eddyb
    • [ ] @pnkfelix (on vacation)

    Niko Matsakis at 2016-08-22 14:06:30

  99. I wonder if tokio-based libraries will end up using and_then a lot. That would be one argument for letting foo().?bar() be shorthand for foo().and_then(move |v| v.bar()), so that Results and Futures can use the same notation.

    nielsle at 2016-08-22 15:23:04

  100. Just to be clear, this FCP is about the question_mark feature, not catch, correct? The title of this issue implies the tracking of both of those features in this one issue.

    Sean McArthur at 2016-08-22 15:53:52

  101. @seanmonstar the latter hasn't even been implemented, so, yeah. Presumably if the FCP results in acceptance this will be changed to track catch.

    Alex Burka at 2016-08-22 16:25:45

  102. Just to be clear, this FCP is about the question_mark feature, not catch, correct? The title of this issue implies the tracking of both of those features in this one issue.

    Yes, just the question_mark feature.

    Niko Matsakis at 2016-08-22 16:53:51

  103. Following up on https://github.com/rust-lang/rfcs/issues/1718#issuecomment-241764523 . I had thought the current behavior ? could be generalized into "bind current continuation", but the map_err(From::from) part of ? makes it slightly more than just bind :/. If we add a From instance for result altogether, then I suppose the semantics could be v? => from(v.bind(current-continuation)).

    John Ericson at 2016-08-23 16:43:50

  104. There has been a lot of discussion about the relative merits of ? in this internals thread:

    https://internals.rust-lang.org/t/the-operator-will-be-harmful-to-rust/3882/

    I don't have time to do a deep summary just now. My recollection is that the comments are focused on the question of whether ? is sufficiently visible to draw attention to errors, but I'm probably overlooking other facets of the discussion. If someone else has time to summarize, that would be great!

    Niko Matsakis at 2016-09-02 16:55:52

  105. I haven't commented before and this maybe way too late, but I find the ? operator confusing as well, if it is used as a hidden return statement, as @hauleth pointed out in the discussion you have linked @nikomatsakis.

    With try!, it was obvious that there might be a return somewhere, because a macro can do that. With the ? as a hidden return, we would have 3 ways to return values from a function:

    • implicit return
    • explicit return
    • ?

    I do like this though, as @CryZe said:

    This way it's familiar to everyone, it pipes down the error to the end, where you can handle it, and there's no implicit returns. So it could roughly look like this:

    let a = try!(x?.y?.z);

    That helps both make the code more concise and doesn't hide a return. And it's familiar from other languages such as coffeescript.

    Conrad Kleinespel at 2016-09-02 17:24:59

  106. How would making ? resolve on the expression level instead of the function level affect futures? For all other use cases it seems okay for me.

    est31 at 2016-09-02 17:35:12

  107. I do like this though, as @CryZe said:

    This way it's familiar to everyone, it pipes down the error to the end, where you can handle it, and there's no implicit returns. So it could roughly look like this:

    let a = try!(x?.y?.z);

    I have postulated this. I think it would be perfect solution.

    Łukasz Jan Niemier at 2016-09-02 17:46:45

  108. With try!, it was obvious that there might be a return somewhere, because a macro can do that.

    It is only obvious because you're familiar with how macros work in Rust. Which is going to be the exact same once ? is stable, widespread and explained in every intro to Rust.

    Georg Brandl at 2016-09-02 17:47:22

  109. @conradkleinespel

    we would have 3 ways to return values from a function:

    • implicit return

    Rust doesn't have "implicit returns", it has expressions that evaluate to a value. This is an important difference.

    if foo {
        5
    }
    
    7
    

    If Rust had "implicit return", this code would compile. But it doesn't, you need return 5.

    Steve Klabnik at 2016-09-02 17:54:11

  110. Which is going to be the exact same once ? is stable, widespread and explained in every intro to Rust.

    For an example of what that might look like, https://github.com/rust-lang/book/pull/134

    Steve Klabnik at 2016-09-02 17:55:22

  111. With try!, it was obvious that there might be a return somewhere, because a macro can do that. It is only obvious because you're familiar with how macros work in Rust. Which is going to be the exact same once ? is stable, widespread and explained in every intro to Rust.

    In any language that I am aware of "macros" mean "here be dragons" and that there anything can happen. So I would rephrase that to "because you're familiar with how macros work", without "in Rust" part.

    Łukasz Jan Niemier at 2016-09-02 18:11:37

  112. @hauleth

    let a = try!(x?.y?.z);

    I have postulated this. I think it would be perfect solution.

    I strongly disagree. As you will get a magic symbol that only works in try! and not outside.

    kennytm at 2016-09-02 18:20:31

  113. @hauleth

    let a = try!(x?.y?.z); I have postulated this. I think it would be perfect solution. I strongly disagree. As you will get a magic symbol that only works in try! and not outside.

    I haven't said that ? should work only in try!. What I was saying is that ? should work like pipe operator that would push data down the stream and return error as soon as it occurred. try! would not be needed in that case, but could be used in the same context as it is used now.

    Łukasz Jan Niemier at 2016-09-02 18:24:19

  114. @steveklabnik I think of Rust as a language having implicit return, In your example, 5 was not returned implicitly, but lets take this:

    fn test() -> i32 {
        5
    }
    

    Here, 5 is implicitly returned, isn't it ? As opposed to return 5; you would need in your example. This makes for 2 different ways to return a value. Which I find somewhat confusing about Rust. Adding a third would not help IMO.

    Conrad Kleinespel at 2016-09-02 18:50:02

  115. It is not. It's the result of an expression, specifically, the function body. "implicit return" implies that you can somehow implicitly return from anywhere, but that's not true. No other expression-based language calls this an "implicit return", as that would be my code sample above.

    Steve Klabnik at 2016-09-02 18:54:46

  116. @steveklabnik Alright, thanks for taking the time to explain this :+1:

    Conrad Kleinespel at 2016-09-02 18:58:21

  117. It's all good! I can totally see where you're coming from, it's just two different things that people use in a wrong way often. I have seen people assume "implicit return" means that you can just leave the ; off anywhere in source to return.... that would be very bad :smile:

    Steve Klabnik at 2016-09-02 19:00:57

  118. @hauleth The ? operator would just be syntactic sugar for and_then in that case. That way you could use it in a lot more cases and it wouldn't have to be a hard to miss return. This is also what all other languages do that have a ? operator. Rust's ? operator in the current implementation would be the exact OPPOSITE of what all other languages do. Also and_then is the functional approach and is encouraged anyway, as it has a clear control flow. So just making ? syntactic sugar for and_then and then keeping the current try! for explicitly "unwrapping and returning", seems to be the much cleaner situation, by making the returns more visible and the ? operator more flexible (by being able to use it in non-return cases like pattern matching).

    Christopher Serr at 2016-09-02 19:05:08

  119. Exactly.

    Łukasz Niemier lukasz@niemier.pl

    Wiadomość napisana przez Christopher Serr notifications@github.com w dniu 02.09.2016, o godz. 21:05:

    @hauleth https://github.com/hauleth The ? operator would just be syntactic sugar for and_then in that case. That way you could use it in a lot more cases and it wouldn't have to be a hard to miss return. This is also what all other languages do that have a ? operator. Rust's ? operator in the current implementation would be the exact OPPOSITE of what all other languages do. Also and_then is the functional approach and is encouraged anyway, as it has a clear control flow. So just making ? syntactic sugar for and_then and then keeping the current try! for explicitly "unwrapping and returning", seems to be the much cleaner situation, by making the returns more visible and the ? operator more flexible (by being able to use it in non-return cases like pattern matching).

    — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/rust-lang/rust/issues/31436#issuecomment-244461722, or mute the thread https://github.com/notifications/unsubscribe-auth/AARzN5-w4EO9_FwNMDpvtYkGUuQKGt-Kks5qmHOHgaJpZM4HUm_-.

    Łukasz Jan Niemier at 2016-09-02 19:06:52

  120. And when working on a Pull Request for the Rust repo I actually had to work with code that used the ? operator and in fact it really hurt readability for me, as it just like ; was super hidden (mentally, cause it's just noise that gets filtered out in the brain) and I overlooked it a lot. And I find that quite scary.

    Christopher Serr at 2016-09-02 19:10:14

  121. @steveklabnik we call it "implicit return", because we aren't the only ones.

    Łukasz Jan Niemier at 2016-09-02 19:27:07

  122. @hauleth huh, in all of my years of Ruby, I've never heard of anyone calling it implicit return. I still maintain that it's the wrong way to think about it.

    Steve Klabnik at 2016-09-02 19:38:37

  123. I've used ? in a few projects and have preferred it to try!, mostly because it is in postfix position. In general, "real" Rust code pretty much enters the Result 'monad' at main and never leaves it except at occasional leaf nodes; and such code is just expected to propagate errors always. For the most part it does not matter which expressions are generating errors - they are all just being sent back up the stack, and I don't want to see that when I'm reading through the main logic of the code.

    My main concern with ? is that I could get the same benefit - postfix position - with method macros, if they existed. I have other concerns that perhaps by stabilizing the current formulation we are limiting future expressivity in error handling - the current Result conversion is not sufficient to make error handling in Rust as ergonomic as I'd like; we've already made several design mistakes with Rust error handling that look difficult to fix, and this may be digging us in deeper; though I have no concrete evidence.

    Brian Anderson at 2016-09-02 21:35:20

  124. I wrote this many times before but I am absolutely in love with ? and the possibilities of the carrier trait. I converted one project over to using ? entirely and it made many things possible (particularly with regards to chaining) which was too complex with try!. I also for fun went over some other projects to see how they would do with ? and overall I have not encountered any problems with that.

    As such I give a huge +1 to stabilizing ? on the basis of a better named Carrier trait which ideally also covers some of the other cases that I brought up in the other discussion about it.

    Armin Ronacher at 2016-09-02 22:31:52

  125. My main concern with ? is that I could get the same benefit - postfix position - with method macros, if they existed.

    Maybe we need an RFC for this? Most people seem to like the functionality of ?, but not the ? itself.

    keeperofdakeys at 2016-09-03 02:30:50

  126. Maybe we need an RFC for this? Most people seem to like the functionality of ?, but not the ? itself.

    There is an RFC with lots of discussion for this. Also, I don't know where you're getting that "most people" from. If it's from the participants in this issue, of course you'll see more people arguing against because stabilizing is already the default action of the team. The ? has been discussed hugely before the RFC was merged, and as a supporter it's kind of tiring to have to do the same thing when stabilization is discussed.

    Anyway, I'll put in my +1 for @mitsuhiko's sentiments here.

    Georg Brandl at 2016-09-03 05:32:41

  127. There is an RFC with lots of discussion for this. Also, I don't know where you're getting that "most people" from. If it's from the participants in this issue, of course you'll see more people arguing against because stabilizing is already the default action of the team.

    Sorry, my comment was too brief. I was referring to creating an RFC for some kind of "method macros", for example func1().try!().func2().try!() (As far as I know, this isn't currently possible).

    Personally I do like the ? operator, but I share the same concerns as @brson, and I think it it would be good to explore alternatives before we stabilise this feature. Including the RFC conversion, this thread, and the internals thread that @nikomatsakis linked, there is definitely still some contention about this feature, even if it is the same arguments over and over again. However if there are no viable alternatives, stabilising does make the most sense.

    keeperofdakeys at 2016-09-03 06:05:25

  128. It seems premature to stabilize a feature without having it fully implemented -- in this case, the catch { .. } expression.

    I have expressed my concerns over this feature before, and I still believe it's a bad idea. I think having a postfix conditional return operator is unlike anything in any other programming language, and is pushing Rust past its already stretched complexity budget.

    Matthew McPherrin at 2016-09-03 06:31:01

  129. @mcpherrinm Other languages have instead hidden unwinding paths at every call for error handling, would you call operator() an "conditional return operator"?

    As for the complexity budget, it's only syntactically different from try!, at least the part that you're complaining about. Is the argument against try!-heavy code, which ? only makes more readable? If so, then I'd agree if there a serious alternative other than "don't have any error propagation automation at all".

    Eduard-Mihai Burtescu at 2016-09-03 06:58:45

  130. Suggesting a compromise: https://github.com/rust-lang/rfcs/pull/1737

    It may have no chances to get accepted, but I'm trying anyway.

    est31 at 2016-09-03 09:28:15

  131. I like @keeperofdakeys's idea about "method macros". I don't think the ? syntax should be accepted for the same reason the ternary operator is not in rust -- readability. The ? itself doesn't say anything. Instead, I would much rather see the ability to generalize ?'s behavior with the "method macros".

    a.some_macro!(b);
    // could be syntax sugar for
    some_macro!(a, b); 
    
    a.try!();
    // could be syntax sugar for
    try!(a); 
    

    This way, it would be clear what the behavior is, and it allows for easy chaining.

    Jakub Hlusička at 2016-09-03 11:14:46

  132. Method macro like result.try!() seems to be a more generic improvement to language ergonomics and feels less ad-hoc than a new ? operator.

    Lu, Wangshan at 2016-09-03 12:40:21

  133. @brson

    I have other concerns that perhaps by stabilizing the current formulation we are limiting future expressivity in error handling - the current Result conversion is not sufficient to make error handling in Rust as ergonomic as I'd like

    This is an interesting point. It'd be worth spending some focused time on this (perhaps you and I can chat a bit). I agree we could do better here. The proposed design for a Carrier trait (see https://github.com/rust-lang/rfcs/issues/1718) may help here, particularly if combined with specialization, since it makes things more flexible.

    Niko Matsakis at 2016-09-03 18:32:00

  134. I really doubt that method macros would be a good extension to the language.

    macro_rules! macros are currently declared in an analogous way to free functions, and will become even more analogous when the new import system is adopted for them. What I mean is that they are declared like top level items and invoked like top level items, and soon they will also be imported like top level items.

    This is not how methods work. Methods have these properties:

    1. Cannot be declared in a module scope, but must be declared within an impl block.
    2. Are imported with the type/trait the impl block is associated with, rather than imported directly.
    3. Are dispatched on the basis of their receiver type, rather than being dispatched based on being an single, unambiguous symbol in this scope.

    Because macros are expanded before typechecking, none of these properties could be true of macros using the method syntax as far as I can tell. Of course we could just have macros that use method syntax but are dispatched and imported the same way as 'free' macros, but I think the disparity would make that a very confusing feature.

    For these reasons I don't think its a good choice to delay ? on the belief that "method macros" may someday appear.

    Moreover, I think there is a line at which some construct is so widely used and important that it should be promoted from macros to sugar. for loops are a good example. The ? behavior is integral to Rust's error handling story, and I think it is appropriate for it to be first class sugar instead of a macro.

    srrrse at 2016-09-04 03:09:56

  135. @hauleth, @CryZe

    To respond to those suggesting that ? should be an and_then operator, this works well in languages like Kotlin (I'm not familiar with coffeescript) due to their extensive use of extension functions but it's not so straightforward in rust. Basically, most uses of and_then are not maybe_i.and_then(|i| i.foo()), they're maybe_i.and_then(|i| Foo::foo(i)) The former could be expressed as maybe_i?.foo() but the latter can't. One could say that Foo::foo(maybe_i?, maybe_j?) turns into maybe_i.and_then(|i| maybe_j.and_then(|j| Foo::foo(i, j))) but this feels even more confusing than just saying that rust early returns on hitting the first ? that evaluates to an error. However, this would arguably be more powerful.

    Steven Allen at 2016-09-04 14:41:13

  136. @Stebalien In the accepted RFC, catch { Foo::foo(maybe_i?, maybe_j?) } does what you want.

    Eduard-Mihai Burtescu at 2016-09-04 15:15:48

  137. @eddyb Good point. I guess I can leave off the "However, this would arguably be more powerful". It comes down to implicit catch/explicit try versus explicit catch/implicit try:

    let x: i32 = try Foo::foo(a?.b?.c()?));
    let y: Result<i32, _> = Foo::foo(a?.b?.c()?);
    

    Versus:

    let x: i32 = Foo::foo(a?.b?.c()?);
    let y: Result<i32, _> = catch  Foo::foo(a?.b?.c()?);
    

    (modulo syntax)

    Steven Allen at 2016-09-04 15:33:48

  138. @Stebalien Another example: if I wanted to pass Foo to a function bar, with your proposal I'd need to:

    bar(Foo::foo(a?.b?.c()?)?)
    

    Is this what you have in mind? Note the extra ?, without it bar would get a Result instead of a Foo.

    Eduard-Mihai Burtescu at 2016-09-04 16:21:10

  139. @eddyb Probably. Note: I'm not actually proposing this! I'm arguing that using ? as a pipe-operator isn't particularly useful in rust without some way to handle the Foo::foo(bar?) case.

    Steven Allen at 2016-09-04 17:14:58

  140. Just to note that I hate the idea of method macros and I can't think of a language feature I would oppose more strongly. They fuzz the phasing of the compiler, and unless we make really quite fundamental changes to the language there is no way they can exist and have unsurprising behaviour. They are also hard to parse sensibly and almost certainly not backwards compatible.

    Nick Cameron at 2016-09-05 02:03:15

  141. @Stebalien, with ? as pipe operator Foo::foo(bar?) would look like this: Foo::foo(try!(bar)) and bar(Foo::foo(a?.b?.c()?)?) (assuming that Foo::foo : fn(Result<_, _>) -> Result<_, _>): bar(try!(Foo::foo(a?.b?.c()?))).

    Łukasz Jan Niemier at 2016-09-05 06:26:37

  142. @hauleth my point was that Foo::foo(bar?)? is much more common than bar?.foo()? in rust. Therefore, to be useful, ? would have to support this case (or some other feature would have to be introduced). I was postulating a way to do so and showing that that way at least would be messy. The entire point of ? is to be able to avoid writing try!(foo(try!(bar(try!(baz()))))) (2x the parentheses!); it usually isn't possible to re-write this as try!(baz()?.bar()?.foo()).

    Steven Allen at 2016-09-05 13:39:15

  143. But you can always do:

    try!(baz().and_then(bar).and_then(foo))
    

    Łukasz Niemier lukasz@niemier.pl

    Wiadomość napisana przez Steven Allen notifications@github.com w dniu 05.09.2016, o godz. 15:39:

    @hauleth https://github.com/hauleth my point was that Foo::foo(bar?)? is much more common than bar?.foo()? in rust. Therefore, to be useful, ? would have to support this case (or some other feature would have to be introduced). I was postulating a way to do so and showing that that way at least would be messy. The entire point of ? is to be able to avoid writing try!(foo(try!(bar(try!(baz()))))) (2x the parentheses!); it usually isn't possible to re-write this as try!(baz()?.bar()?.foo()).

    — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/rust-lang/rust/issues/31436#issuecomment-244749275, or mute the thread https://github.com/notifications/unsubscribe-auth/AARzN1Hdk6uk5-SoYawtgAbJUDf_8MsMks5qnBumgaJpZM4HUm_-.

    Łukasz Jan Niemier at 2016-09-05 13:41:27

  144. On a slightly related note, it seems that the ?-feature is mainly used by builders, so we could possibly avoid the need for the ?-feature by providing an easy way to construct builders that wrap a Result<T,E>. I proposed something here, but it may need a little more work.

    https://github.com/colin-kiegel/rust-derive-builder/issues/25

    nielsle at 2016-09-05 13:55:34

  145. Thanks for your thoughts on the method macro idea @nrc and @withoutboats, it's good to hear some concrete reasons why they wouldn't work.

    keeperofdakeys at 2016-09-05 14:33:38

  146. @nielsle I don't think its accurate to say that ? is "mainly" being used by builders. While builders are an example where I think the advantage of a lightweight, postfix operator really shines through, I prefer ? to try! in every context.

    srrrse at 2016-09-05 19:11:09

  147. @nielsle regarding futures, I was originally concerned for similar reasons. But, after thinking about it, I think that async / await would supersede any need for ? in that context. They're actually fairly orthogonal: you could do things like (await future)?.bar().

    (Maybe it'd be nice to have a suffix operator instead of the keyword await so parens aren't necessary. Or maybe a carefully-tuned precedence would be enough.)

    Tim at 2016-09-08 04:50:33

  148. I would definitely like to see some documentation written before we stabilize. I looked in the reference and couldn't find any mention. Where should we document this feature?

    Christopher at 2016-09-09 13:21:27

  149. @cbreeden I know @steveklabnik generally avoids documenting unstable features, since there's a chance it will be a waste of time if they are never stabilized. I don't know if we've ever blocked stabilization on documentation writing before.

    Scott Olson at 2016-09-12 17:55:14

  150. @solson You're right, this was probably not the place to bring it up -- or at least shouldn't be related to stabilization questions. I guess I was just imagining a situation where we could decide on stabilizing a feature, but then also require documentation before being released to stable rustc. There is an RFC related to integrating documentation with feature stabilization and release, so I'll just wait for that process to stabilize (but not without proper documentation first, of course)

    Christopher at 2016-09-12 20:30:57

  151. I think the important part of this RFC is to have something of the right side of an expression which acts like try!, because that makes reading sequential/chained usages of "try" much more readable and to have "catch". Originally I was a 100% supporter to use ? as syntax but recently I stumbled about some (clean!) code already using ? which made me aware that outside from simple examples ? is extremely easy overlooked. Which now makes me believe that using ? as syntax for "the new try" might be a big mistake.

    Therefore I propose that it might be a good idea to put up some poll before finalizing it (with notification about it on the Forums) to get a feed-back about using ? or some other symbol(s) as syntax. Optimally with example of usage in a longer function. Note that I am only considering that it might be a good idea to rename ? not to change anything else. The poll could list possible other names which did pop up in the past like ?! (or ??) or just something like "use ? vs. use more than on character".

    Btw. not using ? might also satisfy the people which don't like it because it is the same syntax as other languages optional type. And those which want to make it a optional syntax for rust Option types. (Through this are not concerns I share).

    Additionally I think with such a poll people normally not taking part in the RFC process could be reached. Normally this might not be needed but try! => ? is a very big change for anyone writing and/or reading rust code.

    PS: I put up a gist with the function liked above in different variations ("?","?!","??") through I don't know if there had been more. Also there was a RFC for renaming ? to ?! which was redirected to this discussion.

    Sorry, about possible restarting a already long ongoing discussion :smiley_cat: .

    (Note that ?? is bad if you still want to introduce ? for Option because expr??? would be ambiguous)

    Philipp Korber at 2016-09-15 19:18:19

  152. ? is extremely easy overlooked

    What are we discussing here? Completely unhighlighted code? Regular highlighted code browsing? Or actively looking for ? in a function?

    If I select one ? in my editor, all the other ? in the file are highlighted with a bright yellow background, and the search function also works, so I don't see that last one as posing any difficulty for me.

    As for other cases, I'd rather solve this by better highlighting than end up with ?? or ?!.

    Eduard-Mihai Burtescu at 2016-09-15 19:24:38

  153. @dathinab I think .try!() or something would be even better, but that would require UFCS for macros.

    let namespace = namespace_opt.ok_or(Error::NoEntry).try!();
    

    That way it's hard to miss, but just as easy, if not even easier to type than .unwrap().

    Christopher Serr at 2016-09-15 19:25:30

  154. @eddyb: it's about normal highlighted code, e.g. on github. E.g. when reading through a codebase. From my point of view it feels kind of wrong if I need strong highlighting to not easily overlook a ? which would both introduce another return path (/path to catch) and possible changes the type of an variable from Result<T> to T

    Philipp Korber at 2016-09-15 19:28:06

  155. @CryZe: I agree with you but I don't think we can get this in the near future. And having something which is a little bit shorter than .try!() isn't so bad either.

    Philipp Korber at 2016-09-15 19:32:52

  156. @CryZe I like that syntax too, but @withoutboats mentioned some solid reasons why method macros might hurt the language. On the other hand, I fear that if method macros do appear, I don't think they would play well with ?.

    Jakub Hlusička at 2016-09-15 19:35:45

  157. I'm not inherently against using two characters for this sigil, but I looked at the examples & I didn't find the change made ? seem any more visible to me.

    srrrse at 2016-09-15 20:21:57

  158. Yeah same. I think it needs to be some kind of keyword, as symbols in general are used more for structuring, both in normal language and programming languages.

    Christopher Serr at 2016-09-15 20:25:16

  159. @CryZe: Maybe something like ?try would be kind of a keyword. But to be honest I don't like it (?try).

    Philipp Korber at 2016-09-15 20:27:20

  160. @eddyb

    and the search function also works

    Searching for ? will turn up false positives, while longer versions most likely won't.

    est31 at 2016-09-15 21:20:26

  161. Searching for ? will turn up false positives, while longer versions most likely won't.

    I expect that syntax highlighters will take care of this internally (avoiding false positives). As a matter of fact, they can even insert some form of "might return" marker in the fringe (next to the line numbers).

    Steven Allen at 2016-09-15 21:47:32

  162. For example, screen-2016-09-15-175131

    Steven Allen at 2016-09-15 21:52:11

  163. Wow that definitely helps a lot. But then you really need to make sure you have your editor configured properly, which you can't do all the time (like on GitHub for example).

    2016-09-15 23:52 GMT+02:00 Steven Allen notifications@github.com:

    For example, [image: screen-2016-09-15-175131] https://cloud.githubusercontent.com/assets/310393/18568833/1deed796-7b6d-11e6-99af-75f0d7ddd778.png

    — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/rust-lang/rust/issues/31436#issuecomment-247465972, or mute the thread https://github.com/notifications/unsubscribe-auth/ABYmbjyrt07NXKMUdmlBfaciRZq7uBVEks5qqb4sgaJpZM4HUm_- .

    Christopher Serr at 2016-09-15 21:54:30

  164. @CryZe I'm pretty sure GitHub is just using some open source syntax highlighter; we can patch it :-).

    I think in general it is a good idea for the Rust project to make a recommendation that syntax highlighters for Rust should use a highly visible style on ?, to balance the concerns about visibility.

    srrrse at 2016-09-15 22:03:28

  165. I think ? is the best choice we can make here, and we've been using it pretty heavily in Miri.

    Hearkening back to the original motivation, try!(...) is obtrusive and makes it difficult to ignore the flow of errors and just read the happy-path. This is a downside compared to the invisible exceptions of traditional languages. Expanding ? to a more involved keyword would have the same downside.

    On the other hand, with ?, when I don't care about the flow of errors I can ignore it and let it fade into the background. And when I really do care about the flow of errors, I can still see ? just fine. Highlighting ? brightly is not even necessary for me, but if it helps other people, that's great. This is an improvement over both invisible exceptions and try!.

    Switching to a trivially larger sigil like ?! would not aid me in any way, but make reading and writing error handling code marginally worse.

    Scott Olson at 2016-09-15 23:31:44

  166. Thanks everyone for a hearty final comment period (as well as a prior internals thread). Given that we're talking about the most commented RFC to date, I'm not surprised to see that the discussion around stabilization has also been quite active.

    Let me cut to the chase first: The @rust-lang/lang team has decided to stabilize the ? operator when applied to values of Result type. Note that the catch feature is not being stabilized (and, indeed, has not yet been implemented); similarly, the so-called "carrier trait", which is a means to extend ? to types like Option, is still in the pre-RFC discussion phase. We have however taken steps in the current implementation to ensure that we can add the Carrier trait later (which address some of my earlier concerns about potential interaction with inference).

    I'd like to take a bit of time to summarize the discussion that has taken place since the FCP began on Aug 22. Many of these themes also occurred in the original RFC thread. If you're interested in reading the thread, the FCP comment and recap comment in that thread attempt to cover the conversation in depth. In some cases I will link to comments in that original thread if they are more in-depth than the corresponding ones from this thread.

    The scope of the ? operator ought to be the current expression, not the current function.

    When an error occurs, today's try! macro propagates that error unconditionally to the calling function (in other words, it executes a return with the error). As currently designed, the ? operator follows this precedent, but with the intention of supporting a catch keyword that allows the user to specify a more limited scope. This means, for example, that x.and_then(|b| foo(b)) can be written as catch { foo(x?) }.In contrast, several recent languages use the ? operator to mean something more analogous to and_then, and there has been concern that this may prove confusing to new users.

    Ultimately, once catch is implemented, this is a question of defaults. And there are several reasons that we believe that the default of "break out of function" (with the option to customize) is more appropriate for ? in Rust:

    • the try! macro has proven itself many times over as a very useful default. ? is intended as a replacement for try!, so it's natural for it to behave the same.
    • ? in Rust is primarily in use for propagating results, which is more analogous to exceptions. All exception systems default to propagating errors out of the current function and into the caller (even those which, like Swift, also require a keyword to do so).
      • in contrast, ? in Swift, Groovy, and so forth has to do with null values or option types.
      • if we adopt a carrier trait, the ? operator in Rust can still be used in said scenarios (and in particular in conjunction with catch), but it's not the main use case.
    • many uses of and_then in Rust today would not work using method notation; some foreseen future uses (such as in futures) may be subsumed by an async-await transformation in any case

    The ? obscures control flow because it is hard to spot.

    A common concern is that the ? operator is too easy to overlook. This is clearly a balancing act. Having a lightweight operator makes it easy to focus on the "happy path" when you want to -- but it's important to have some indication of where errors can occur (in contrast to exceptions, which introduce implicit control-flow). Moreover, it is easy to make ? easier to spot through syntax highlighting (e.g., 1, 2).

    Why not method macros?

    One of the big benefits of ? is that it can be used in post-fix position, but we could obtain similar benefits from "method macros" like foo.try!. While true, method macros open up a lot of complexity themselves, particularly if you want them to behave like methods (e.g., dispatched based on the type of the receiver, and not using lexical scope). Moreover, using a method macro like foo.try! has a significantly heavier weight feel than foo? (see the previous point).

    What contracts should From offer?

    In the original RFC discussion, we decided to postpone the question of [whether there should be "contracts" for From]((https://github.com/rust-lang/rust/issues/31436#issuecomment-180558025). The general consensus of the lang team is that one should view the ? operator as invoking the From trait, and that the From trait impls can naturally do whatever is permitted by their type signatures. Note that the role of the From trait is quite limited here anyway: it is simply used to convert from one sort of error to another (but always in the context of a Result).

    However, I would like to note that the discussion regarding a "carrier" trait is ongoing, and adopting strong conventions are more important in that scenario. In particular, the carrier trait gets to define what constitutes "success" and "failure" for a type, as well as whether one kind of value (e.g., an Option) can be converted into another (e.g., a Result). Many have argued that we do not want to support arbitrary interconversions between "error-like" types (e.g., ? should not be able to convert Option to Result). Obviously, since end-users can implement the Carrier trait for their own types as they choose, this is ultimately a guideline, but I think it's an important one.

    Niko Matsakis at 2016-10-05 13:54:20

  167. port try! to use ?

    I don't think we can ever do this backwards compatibly and we should leave the implementation of try! alone. Should we deprecate try!?

    Nick Cameron at 2016-10-11 19:42:46

  168. @nrc Why isn't it backward compatible?

    srrrse at 2016-10-11 19:55:40

  169. @withoutboats try!(x) is (x : Result<_, _>)? and we could probably implement it that way if we wanted to, but in general x? could infer to anything that supports the Carrier trait (in the future), one example is iter.collect()? which with try! would only be Result but can realistically be Option.

    Eduard-Mihai Burtescu at 2016-10-11 19:57:52

  170. That makes sense. I thought we accepted that adding impls to std could cause inference ambiguities; why not also accept that here?

    Either way, I do think try! should be deprecated.

    srrrse at 2016-10-11 20:01:49

  171. ? is more useful in a builder pattern like context, while try is more useful in a nested method like context. I think it should not be deprecated, whatever that means.

    est31 at 2016-10-12 03:38:27

  172. @est31 They do exactly the same thing right now except for inference. Not sure what you mean exactly, but ? is usually strictly cleaner (again modulo inference). Could you give examples?

    Eduard-Mihai Burtescu at 2016-10-12 03:45:09

  173. @eddyb foo()?.bar()?.baz() is nicer than try!(try!(foo()).bar()).baz(), and try!(bar(try!(foo()))) is nicer than bar(foo()?)?

    est31 at 2016-10-12 03:49:32

  174. I find ? more readable in both cases. It reduces the unnecessary parentheses clutter.

    Scott Olson at 2016-10-12 03:51:44

  175. When will this land on stable?

    Ofek Lev at 2016-10-19 00:43:14

  176. @ofek this is for the whole thing, which is not yet complete, so it's hard to say. https://github.com/rust-lang/rust/pull/36995 stabilized the basic ? syntax, which should be stable in 1.14.

    Steve Klabnik at 2016-10-19 00:44:46

  177. Don't forget to add the ? operator to the reference now that its stable: https://doc.rust-lang.org/nightly/reference.html#unary-operator-expressions

    And @bluss pointed out that the book is outdated as well: https://doc.rust-lang.org/nightly/book/syntax-index.html

    est31 at 2016-10-23 01:57:56

  178. @tomaka

    I'm opposed to extending ? to Options.

    It doesn't have to be used for error purposes. For example, if I hape a wrapper method for taking get and mapping it by some function, then I'd like to be able to propagate the None case up.

    ticki at 2016-10-29 15:05:37

  179. ? was pitched as being just for errors; the more general do notation for general propagation like this was a non-goal.

    On Oct 29, 2016, 11:08 -0400, ticki notifications@github.com, wrote:

    @tomaka (https://github.com/tomaka)

    I'm opposed to extending ? to Options.

    It doesn't have to be used for error purposes. For example, if I hape a wrapper method for taking get and mapping it by some function, then I'd like to be able to propagate the None case up.

    — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub (https://github.com/rust-lang/rust/issues/31436#issuecomment-257096575), or mute the thread (https://github.com/notifications/unsubscribe-auth/AABsipGIpTF1-7enk-z_5JRYYtl46FLPks5q42DCgaJpZM4HUm_-).

    Steve Klabnik at 2016-10-29 17:34:07

  180. This (= ? itself) is now a stable feature! (From Rust 1.13)

    Two documentation issues:

    • [x] ~~Update Error Handling chapter in the book #37750~~
    • [x] ~~Update Carrier trait docs for current situation #37751~~

    bluss at 2016-11-13 11:52:02

  181. Note that neither catch nor the Carrier trait have been properly implemented yet, just the bare ? feature.

    Nick Cameron at 2016-11-13 20:26:13

  182. Carrier exists, and error messages refer to it when using ?, so it would be better if the Carrier issue had been fixed rather than rejected. Its docs also need updating, since the docs refer to it being implemented for Option. (Which is false).

    bluss at 2016-11-13 20:38:13

  183. #35946 should remove any mention of Carrier from the error messages. It does sound like we should at least remove the Option mention from the Carrier docs.

    Nick Cameron at 2016-11-13 20:56:01

  184. I'm adding T-libs to this issue due to the interaction with the Carrier trait

    Alex Crichton at 2017-02-08 17:06:56

  185. Question that @cramertj encountered: https://internals.rust-lang.org/t/grammatical-ambiguity-around-catch-blocks/4807

    Niko Matsakis at 2017-02-17 20:57:47

  186. Hi; in #31954 the upcast for the error type is done using From (and similar the same in current head), but RFC 243 clearly states Into should be used for the conversion.

    Is there any reason why From was used instead? When trying to upcast to some generic error type (i.e. io::Error, or something else in an external crate) From cannot be implemented for local error types.

    Stefan Bühler at 2017-05-05 14:40:05

  187. (FWIW as the author of RFC 243 I didn't think very hard about whether From or Into is preferable, and may or may not have made the right choice. Which is just to say that the question should be decided based on the merits (which at this point may include backwards compatibility), rather than what's written in the RFC.)

    Gábor Lehel at 2017-05-05 15:54:39

  188. Sadly there would be a regression. If no (non-trivial) From<...> instance for an error type is implemented, the compiler can deduce the type in certain cases, where it cannot deduce it when using Into (the set of From instances is limited by the current crate, the full set of Into instances is not known while compiling a crate).

    See https://play.rust-lang.org/?gist=6d3ee9f93c8b40094a80d3481b12dd00 ("simplified" from a real world problem involving fmt::Error in src/librustc/util/ppaux.rs#L81)

    Stefan Bühler at 2017-05-06 12:56:24

  189. There's a new RFC which will supersede the old in terms of describing ?: rust-lang/rfcs/pull/1859

    bluss at 2017-05-06 14:03:50

  190. I’m -1 on having catch blocks at all, when they are much more concise already with the closures.

    Simonas Kazlauskas at 2017-05-09 20:45:39

  191. Closures interfere for flow control statements. (break, continue, return)

    Simon Sapin at 2017-05-11 18:38:52

  192. Now that https://github.com/rust-lang/rfcs/pull/1859 has been merged, and claims that we are re-using this issue as its tracking issue, I would like to propose that we reevaluate the catch portion of RFC 243.

    I'm not proposing that we get rid of it outright, but enthusiasm for catch was never as great as it was for ? and I think it's worth making sure that it still makes sense in light of the idioms that have emerged for ?, and that are expected to emerge in light of Try.

    bstrie at 2017-05-30 20:49:01

  193. I'm still eager to have catch; earlier today I had to contort some code in a way that could have been avoided if catch were stable.

    The feature has been implemented on nightly with the syntax do catch, hasn't it?

    srrrse at 2017-05-30 21:06:42

  194. @withoutboats Yes, it's currently do catch, since catch { ... } conflicts with struct literals (struct catch { }; catch { }). . @archshift As @SimonSapin pointed out above, closures interfere with break, continue, and return.

    Taylor Cramer at 2017-05-30 21:37:33

  195. @bstrie I find that I pretty frequently want catch these days; it comes up a lot during refactoring.

    Aaron Turon at 2017-05-30 22:11:42

  196. I didn't realize that we were planning on requiring do catch as the syntax. Given that the risk of real-world breakage seems exceedingly low (both violates the struct naming guidelines and would have to have the constructor be the first expression in the statement (which is rare outside of return position)), could we perhaps leverage rustfmt to rewrite any offending identifiers to catch_? If it requires Rust 2.0 to do so, then, well, I've always been one to say that Rust 2.0 ought to contain only trivial breaking changes anyway... :P

    bstrie at 2017-05-31 05:28:53

  197. @bstrie We absolutely don't want to require do catch long term. The discussion that led to using that syntax for now is here: https://github.com/rust-lang/rust/pull/39921

    srrrse at 2017-05-31 05:39:43

  198. Excellent, thanks for the context.

    bstrie at 2017-05-31 05:43:07

  199. I just came here because I hoped catch was stable, and had to learn it is not -- so yes, absolutely, now that ? is stable, it'd be great to also have catch.

    Ralf Jung at 2017-05-31 22:26:17

  200. I wanted to see how far we'd gotten on the rest of this and saw the do catch discussion.

    I've been considering a probably silly idea that would help in cases like this: allow optionally prefixing keywords with @ or some other sigil, then make all new keywords use the sigil only. We also had a similar problem with the coroutine discussion. I can't remember if I went into this as a solution there--I might have--but it looks like this may keep coming up.

    Austin Hicks at 2017-06-09 02:18:43

  201. FWIW, C# actually supports the opposite: @ for using keywords as identifiers. That's commonly seen in Razor, where you pass things like new { @class = "errorbox" } to set properties on HTML nodes.

    scottmcm at 2017-06-09 07:47:40

  202. @scottmcm That's interesting. I didn't know about it. But for Rust we have to go the other way because compatibility.

    Neat idea on their part, though. The .net ecosystem has a lot of languages, all with disparate keywords and all able to call each other.

    Austin Hicks at 2017-06-09 13:29:45

  203. One other possibility for the future of keywords: put them behind an attribute, like some sort of stable #[feature].

    I think this problem will need a more general solution in the long run.

    Austin Hicks at 2017-06-14 00:18:10

  204. @camlorn This thread may interest you, specifically Aaron's idea about Rust "epochs": https://internals.rust-lang.org/t/pre-rfc-stable-features-for-breaking-changes/5002

    jimmycuadra at 2017-06-14 00:41:37

  205. I think we should link https://github.com/rust-lang/rust/issues/42327 from the issue description here. (Also maybe the RFC text should be updated to link there instead of here.)

    (EDIT: I posted a comment there, not sure who is or isn't already subscribed to it!)

    Gábor Lehel at 2017-06-28 20:09:51

  206. @camlorn It could, however, open up the "rustfmt does a trivial rewrite for a while, then it becomes a keyword" path. AKA take advantage of the "it's not breaking if there's an extra annotation that could have been written that would make it work in both" escape hatch in the stability guarantee. And, similar to UFCS for inference changes, a hypothetical "store in fully-elaborated form" model could keep old crates working.

    scottmcm at 2017-06-29 08:17:34

  207. I wouldn't mind if the syntax for catch block was just do { … } or even ?{ … }

    Nikita at 2017-09-13 13:40:08

  208. do has the nice property of already being a keyword. It has the dubious (depending on your perspective) property of invoking Haskell-like do-notation, though that never stopped its past uses, and this one is somewhat closer in use case.

    Russell Johnston at 2017-09-13 17:25:52

  209. it also looks like, but behaves different than javascripts proposed do expressions

    Hendrik Sollich at 2017-09-13 19:29:38

  210. That proposal isn't really relevant to Rust, which already uses bare blocks as expressions.

    Russell Johnston at 2017-09-13 20:23:54

  211. I was just pointing out the possible confusion since both would look the same but do entirely different things.

    Hendrik Sollich at 2017-09-14 07:24:37

  212. It has the dubious (depending on your perspective) property of invoking Haskell-like do-notation

    @rpjohnst Result and Option are monads, so it's at least conceptually similar. It should also be forward-compatible to extend it in the future to support all the monads.

    Nikita at 2017-09-14 10:59:08

  213. Right, in the past the objection has been that if we add do for something conceptually similar to monads, but without fully supporting them, it would make people sad.

    At the same time, while we could probably make it forward-compatible with full do-notation, we probably shouldn't add full do-notation. It is not composable with control structures like if/while/for/loop or break/continue/return, which we must be able to use inside and across catch (or in this case do) blocks. (This is because full do-notation is defined in terms of higher order functions, and if we stuff the contents of a catch block into a series of nested closures, suddenly control flow all breaks.)

    So in the end this downside of do is that it looks like do-notation without actually being do-notation, and without a good path forward to becoming do-notation either. Personally, I'm totally fine with this because Rust isn't gonna get do notation anyway- but that's the confusion.

    Russell Johnston at 2017-09-14 16:32:44

  214. @nikomatsakis , #42526 is merged, you can mark it as done in tracking list :)

    tvladyslav at 2017-10-14 19:43:38

  215. Is it possible to make the catch keyword contextual but disable it if a struct catch is in scope, and issue a deprecation warning?

    Parker Snell at 2017-10-14 21:56:58

  216. Not sure how appropriate this is but I ran into an issue which maybe this needs to solve in that sometimes you want to abort an ok rather than error value which is currently possible when you use return insofar that you often want to abort an inner error through an outer okay. Like when a function say returns Option<Result<_,_>> which is quite common from say an iterator.

    In particular I have a macro in a project which I use copiously right now:

    macro-rules! option_try {
    	( $expr:expr ) => {
    		match $expr {
    			Ok(x)  => x,
    			Err(e) => return Some(Err(e.into())),
    		}
    	}
    }
    

    It's very common that a function called within an Iterator::next implementation needs to immediately abort with Some(Err(e)) on failure. This macro works inside of a normal function body but not inside of a catch block because catch doesn't grab return categorically but just the special ? syntax.

    Though in the end labeled-returns would make the entire catch block idea redundant wouldn't they?

    Definitely13OrOlderISwear at 2018-01-21 20:37:53

  217. It looks like #41414 is done. Could someone update the OP?

    mark at 2018-04-29 23:10:19

  218. Update: RFC rust-lang/rfcs#2388 is now merged and so catch { .. } is to be replaced with try { .. }. See the tracking issue right above this comment.

    Mazdak Farrokhzad at 2018-05-03 07:50:22

  219. What does this mean for Edition 2015, is the do catch { .. } syntax still on a path towards stabilization, or will this be dropped and only supported via try { .. } in Edition 2018+?

    Nemo157 at 2018-05-03 09:15:34

  220. @Nemo157 The latter.

    Mazdak Farrokhzad at 2018-05-03 09:18:01

  221. Is there a two-statement try ... catch ... in the current proposal? If so, I don’t get the semantics.

    Anyway, if this proposal is only about desugaring then I’m cool with it. i.e. Does a catch block just change where the ? operator exits to?

    Alexander Regueiro at 2018-06-06 17:32:22

  222. As all check boxes are ticked in the top post, when would us move forward? If there are still unresolved issues we need to have new check box(es) added.

    earthengine at 2018-10-03 02:04:22

  223. There are probably plenty of remaining unresolved questions not recorded. For example, the ok-wrapping behavior is not settled within the lang team, the design of the Try is not finalized, and so on. We should probably split this issue up into several more targeted ones as it has likely outlived it's usefulness.

    Mazdak Farrokhzad at 2018-10-03 02:08:47

  224. Hmm... it bothers me that these questions haven't been recorded.

    mark at 2018-10-03 14:12:42

  225. @mark-i-m So to clarify, I think they have been somewhere; but not in one location; it's a bit scattered atm in various RFCs and issues and such, so what we need to do is record them in the proper locations.

    Mazdak Farrokhzad at 2018-10-03 15:08:11

  226. The design of the backing trait is tracked in https://github.com/rust-lang/rust/issues/42327; there's extensive discussion there about weaknesses in the current one and a possible new direction. (I'm planning of making a pre-RFC for a change there once 2018 settles a bit.)

    So I think only try{} is left here, and the only disagreement I know of for that are things that were settled in the RFC and re-confirmed in one of the above-mentioned issues. It could still be good to have a distinct tracking issue, though.

    I'll add a checkbox for the one pending implementation task I know still needs to be done...

    scottmcm at 2018-10-03 16:27:35

  227. @scottmcm I know @joshtriplett had concerns about OK-wrapping (noted in the try RFC) and I'd personally like to restrict break in the initial stabilization of try { .. } so that you can't do loop { try { break } } and such.

    Mazdak Farrokhzad at 2018-10-03 16:34:16

  228. @Centril

    so that you can't do loop { try { break } }

    Right now, you cannot use break in a non-loop block, and it is correct: break should only be used in loops. To early leave a try block, the standard way is to write Err(e)?. and it forces that early leaves are always in the "abnormal" control path.

    So my proposal is the code you shown should be allowed, and it should break the loop, not just leaving the try.

    The immediate benefit, is when you see break you know it is going break from a loop, and you can always replace it with a continue. Also, it removes the need to having to label the break point when using try blocks inside a loop and you want to exit the loop.

    earthengine at 2018-10-03 23:31:38

  229. @Centril Thank you for raising those.

    Regarding break, I personally would be fine with simply saying that try doesn't care about break and it passes through to the containing loop. I just don't want break to interact with try at all.

    As for Ok-wrapping, yes, I'd like to address that before stabilizing try.

    Josh Triplett at 2018-10-04 17:07:47

  230. @centril Yes, I'm aware. But it's important to remember that that's re-re- raising the issue. The RFC decided to have it, it was implemented without it, but then the original intent was taken again, and the implementation changed to follow the RFC. So my big question is whether any material facts have changed, especially given that this is one of the noisiest topics I've ever seen discussed on RFCs+IRLO.

    scottmcm at 2018-10-05 05:40:42

  231. @scottmcm Of course, as you know, I agree with retaining Ok-wrapping ;) and I agree that the issue should be considered settled.

    Mazdak Farrokhzad at 2018-11-08 19:40:28

  232. I just wanted to comment on this, not sure if this is the right thing:

    Essentially, a situation I have is callbacks in a GUI framework - instead of returning an Option or Result, they need to return a UpdateScreen, to tell the framework if the screen needs to be updated or not. Often I don't need logging at all (it's simply not practical to log on every minor error) and simply return a UpdateScreen::DontRedraw when an error has occurred. However, with the current ? operator, I have to write this all the time:

    let thing = match fs::read(path) {
        Ok(o) => o,
        Err(_) => return UpdateScreen::DontRedraw,
    };
    

    Since I can't convert from a Result::Err into a UpdateScreen::DontRedraw via the Try operator, this gets very tedious - often I have simple lookups in hash maps that can fail (which isn't an error) - so often in one callback I have 5 - 10 usages of the ? operator. Because the above is very verbose to write, my current solution is to impl From<Result<T>> for UpdateScreen like this, and then use an inner function in the callback like this:

    fn callback(data: &mut State) -> UpdateScreen {
         fn callback_inner(data: &mut State) -> Option<()> {
             let file_contents = fs::read_to_string(data.path).ok()?;
             data.loaded_file = Some(file_contents);
             Some(())
         }
        
        callback_inner(data).into()
    }
    

    Since the callback is used as a function pointer, I can't use an -> impl Into<UpdateScreen> (for some reason, returning an impl is currently not allowed for function pointers). So the only way for me to use the Try operator at all is to do the inner-function trick. It would be nice if I could simply do something like this:

    impl<T> Try<Result<T>> for UpdateScreen {
        fn try(original: Result<T>) -> Try<T, UpdateScreen> {
            match original {
                 Ok(o) => Try::DontReturn(o),
                 Err(_) => Try::Return(UpdateScreen::DontRedraw),
            }
        }
    }
    
    fn callback(data: &mut State) -> UpdateScreen {
         // On any Result::Err, convert to an UpdateScreeen::DontRedraw and return
         let file_contents = fs::read_to_string(data.path)?;
         data.loaded_file = Some(file_contents);
         UpdateScreen::Redraw
    }
    

    I am not sure if this would be possible with the current proposal and just wanted to add my use-case for consideration. It would be great if a custom Try operator could support something like this.

    Felix Schütt at 2018-11-25 01:29:07

  233. EDIT: I made a mistake.


    Ignore this post


    Could this play better with type inference, it fails even in simple cases.

    fn test_try(a: u32, b: u32) {
        let div = if b != 0 {
            Some(a / b)
        } else {
            None
        };
        
        let x // : Option<_> // why is this type annotation necessary
        = try { div? + 1 };
        
        println!("{:?}", x);
    }
    

    If this is re-written to use a closure instead of the try block (and in the process loose auto wrapping), then we get

    fn test_closure(a: u32, b: u32) {
        let div = if b != 0 {
            Some(a / b)
        } else {
            None
        };
        
        let x =  (|| (div? + 1).into())();
        
        println!("{:?}", x);
    }
    

    Which doesn't require a type annotation, but it does require that we wrap the result.

    playground

    RustyYato at 2018-12-12 22:02:12

  234. @KrishnaSannasi your closure based example has a type inference failure as well (playground) because Into doesn't constrain the output and you don't use it anywhere that does later.

    This seems to mostly be an issue with the Try trait rather than try blocks, similar to Into it doesn't propagate any type information from the inputs to the output, so the output type must be determinable by its later usage. There's a lot of discussion in https://github.com/rust-lang/rust/issues/42327 about the trait, I haven't read it so I'm not sure if any of the proposals there could fix this issue.

    Nemo157 at 2018-12-13 10:13:07

  235. @Nemo157

    Yes, I did a last minute change to my code, to make it use into, and didn't test that. My bad.

    RustyYato at 2018-12-13 15:03:29

  236. How far are we from stabilizing try blocks? It's the only feature i need from nightly :D

    Arignir at 2019-04-07 13:56:18

  237. @Arignir

    I believe once this is done, it can be stabilized.

    block try{}catch (or other following idents) to leave design space open for the future, and point people to how to do what they want with match instead

    boyned//Kampfkarren at 2019-04-10 02:20:06

  238. Isn't there any middle ground design to allow the feature now while still let the possibility to leave design space open for the future (and therefore an eventual catch block)?

    Arignir at 2019-04-12 16:47:52

  239. The PR I made should check off that box anyway, CC @nikomatsakis

    boyned//Kampfkarren at 2019-04-12 17:01:31

  240. I tried using this for the first time yesterday, and I was a little surprised that this:

    #![feature(try_blocks)]
    fn main() -> Result<(), ()> {
        let x: () = try {
            Err(())?
        }?;
        Ok(x)
    }
    

    does not compile due to

    error[E0284]: type annotations required: cannot resolve `<_ as std::ops::Try>::Ok == _`
    

    Rather, I had to do

    #![feature(try_blocks)]
    fn main() -> Result<(), ()> {
        let x: Result<(), ()> = try {
            Err(())?
        };
        let x = x?;
        Ok(x)
    }
    

    instead.

    This was confusing at first, so maybe it is worth changing the error message or mentioning in --explain?

    Akshay Narayan at 2019-04-26 14:38:23

  241. If you move the question mark in your first example down a bit to

    #![feature(try_blocks)]
    fn main() -> Result<(), ()> {
        let x: () = try {
            Err(())?
        };
        Ok(x?)
    }
    

    You get a better error message. The error comes up because Rust can't decide which type to resolve the try { ... } to, due to how general it is. Because it can't resolve this type, it can't know what the <_ as Try>::Ok type is, which is why you got the error that you did. (because the ? operator unwraps the Try type and gives back the Try::Ok type). Rust can't work with the Try::Ok type on it's own, it must be resolved through the Try trait, and the type that implements that trait. (which is a limitation of the current way type checking works)

    RustyYato at 2019-04-27 00:29:17

  242. Everything for this feature is implemented, correct? If so, how long do we want to look at sitting on this before stabilizing?

    Gabriel Smith at 2019-05-28 21:06:01

  243. I thought it was still an open question of whether we wanted this or not. In particular, there was some discussion about whether we want to use the language of exceptions here (try, catch).

    Personally, I’m strongly against trying to create the impression that Rust has something like exceptions. I think the use of the word catch in particular is a bad idea because anyone coming from a language with exceptions will assume this does unwinding, and it doesn’t. I would expect it to be confusing and painful to teach.

    mark at 2019-05-28 22:29:46

  244. In particular, there was some discussion about whether we want to use the language of exceptions here (try, catch).

    I think https://github.com/rust-lang/rfcs/pull/2388 definitively settled whether try as a name is acceptable. This is not an open question. But the definition of the Try trait as well as Ok-wrapping seem to be.

    Mazdak Farrokhzad at 2019-05-28 22:33:29

  245. Ok-wrapping was already decided in the original RFC, then removed during implementation, and finally re-added later. I don't see how it's an open question.

    Russell Johnston at 2019-05-28 22:39:51

  246. @rpjohnst Well it is by virtue of Josh disagreeing with the decision with the original RFC... :) It's a settled matter for me. See https://github.com/rust-lang/rust/issues/31436#issuecomment-427096703, https://github.com/rust-lang/rust/issues/31436#issuecomment-427252202, and https://github.com/rust-lang/rust/issues/31436#issuecomment-437129491. Anyways... the point of my comment was that try as "language of exceptions" is a settled matter.

    Mazdak Farrokhzad at 2019-05-28 22:43:41

  247. Woah, when did this happen? The last thing I remember was discussions on internals. Im very much against Ok-wrapping too :(

    mark at 2019-05-28 23:16:45

  248. Eww. Can't believe this happened. Ok-wrapping is so horrible (it breaks the very sensible intuition that all return expressions in a function should be of the function's return type). So yeah, definitely with @mark-i-m on this. Is Josh's disagreement enough to keep this an open issue, and get more discussion on it? I'd gladly lend him support in fighting this, not that it means something as a non- team member.

    Alexander Regueiro at 2019-05-29 00:47:32

  249. Ok-wrapping as accepted in RFC 243 (literally the one that defined the ? operator, if you were wondering when this happened) does not change anything about function return expressions' types. This is how RFC 243 defined it: https://github.com/rust-lang/rfcs/blob/master/text/0243-trait-based-exception-handling.md#catch-expressions

    This RFC also introduces an expression form catch {..}, which serves to "scope" the ? operator. The catch operator executes its associated block. If no exception is thrown, then the result is Ok(v) where v is the value of the block. Otherwise, if an exception is thrown, then the result is Err(e).

    Note that catch { foo()? } is essentially equivalent to foo().

    That is, it takes a block of type T and unconditionally wraps it to produce a value of type Result<T, _>. Any return statement in the block is completely unaffected; if the block is the tail expression of a function the function must return a Result<T, _>.

    It's been implemented this way on nightly for ages: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=88379a1607d952d4eae1d06394b50959. This was done after much discussion by the lang team in, and linked from, this thread: rust-lang/rust#41414 (and this is linked at the top of this issue as well).

    Russell Johnston at 2019-05-29 05:06:54

  250. On May 28, 2019 5:48:27 PM PDT, Alexander Regueiro notifications@github.com wrote:

    Eww. Can't believe this happened. Ok-wrapping is so horrible (it breaks the very sensible intuition that all return expressions in a function should be of the function's return type). So yeah, definitely with @mark-i-m on this. Is Josh's disagreement enough to keep this an open issue, and get more discussion on it? I'd gladly lend him support in fighting this, not that it means something as a non- team member.

    Thank you. I'm not just disagreeing with this solely for myself; I'm also representing the numerous people I've seen express the same position I do.

    Josh Triplett at 2019-05-29 06:01:30

  251. @joshtriplett @mark-i-m @alexreg

    Can one of you explain why you find Ok wrapping to be so disagreeable or provide a link to somewhere that it has been explained before? I went looking, but in a cursory view I didn't see anything. I have no horse in this (I quite litterally only commented on this because I saw all the boxes check and no discussion for a month), but now that I have kicked this hornet's nest I want to understand the arguments better.

    Gabriel Smith at 2019-05-29 06:36:34

  252. On Tue, May 28, 2019 at 03:40:47PM -0700, Russell Johnston wrote:

    Ok-wrapping was already decided in the original RFC, then removed during implementation, and finally re-added later. I don't see how it's an open question.

    I think you partly answered your own question. I don't think everyone involved in the original RFC discussion was on the same page; try was absolutely something many people wanted, but there was not consensus for Ok-wrapping.

    Josh Triplett at 2019-05-29 07:59:36

  253. On Tue, May 28, 2019 at 03:44:46PM -0700, Mazdak Farrokhzad wrote:

    Anyways... the point of my comment was that try as "language of exceptions" is a settled matter.

    As a clarification, I don't find the metaphor of "exceptions" appealing, and many of the attempts at things like try-fn and Ok-wrapping seem to attempt to make the language fake having an exception-like mechanism. But try itself, as a means of catching ? at something other than the function boundary, makes sense as a control-flow construct.

    Josh Triplett at 2019-05-29 08:01:21

  254. On Tue, May 28, 2019 at 11:37:33PM -0700, Gabriel Smith wrote:

    Can one of you explain why you find Ok wrapping to be so disagreeable

    As one of a few reasons:

    On Tue, May 28, 2019 at 05:48:27PM -0700, Alexander Regueiro wrote:

    it breaks the very sensible intuition that all return expressions in a function should be of the function's return type

    This breaks various approaches people use for type-directed reasoning about functions and code structure.

    Josh Triplett at 2019-05-29 08:05:43

  255. I certainly have my own thoughts here, but could we please not re-open this topic right now? We just had a contentious 500+ post conversation about syntax, so I'd like to avoid landmines for a while.

    scottmcm at 2019-05-29 08:09:58

  256. If this is blocked on the lang team discussing that, should the "resolve whether catch blocks should "wrap" result value (#41414)" checkbox be unchecked again (maybe with a comment that it's blocked on lang team) so that people looking at this tracking issue know the status?

    Nemo157 at 2019-05-29 08:22:32

  257. Apologies, I'm not trying to reopen anything- just restate what's marked as decided in the tracking issue and when+how that happened.

    Russell Johnston at 2019-05-29 08:38:22

  258. @rpjohnst Thanks for the info!

    @yodaldevoid Josh pretty much summarized my thoughts.

    I am slightly less opposed to ok-wrapping confined to a block (as opposed to affecting the type of a function), but I think it still sets a bad precedent: as Josh said “I don’t find the metaphor of exceptions appealing”

    mark at 2019-05-29 14:51:48

  259. @joshtriplett has essentially summarised my views too: the issues are the suitability of the "exception" metaphor (arguably panics + catch_unwind is much more analagous) and type-based reasoning. I am indeed okay with try blocks as a scoping & control-flow mechanism too, but not the more radical points.

    Okay, fair enough, let's not have the whole debate here... maybe just uncheck the box as suggested, and put it back to lang-team debate (in their own time), using some of the rationale mentioned in this thread? As long as stabilisation is not rushed, that sounds reasonable I suppose.

    Alexander Regueiro at 2019-05-29 16:51:15

  260. Has a syntax for type annotations been agreed? I was hoping for some try { foo()?; bar()?; }.with_context(|_| failure::err_msg("foon' n' barn'")?;, which is not even remotely interested in compiling: error[E0282]: type annotations needed.

    https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=4e60d44a8f960cf03307a809e1a3b5f2

    Chris West at 2019-06-03 19:26:39

  261. I read through the comments a little while ago (and loading 300 comments again on github is way too tedious), but I do recall that most (if not all) of the examples regarding the debate around Try::Ok wrapping used Ok in the example. Considering Option implements Try as well, I would like to know how that impacts the team's position on which side of the debate to be on.

    Every time I use Rust, I keep thinking "man, I really wish I could use a try block here," but about 30% of the time that's because I really wish I could use try for Options (like I used to in Scala, which used the for syntax to apply to monads in general, but is very similar to try here).

    Just today, I was using the json crate and it exposes the as_* methods which return options.

    Using the two syntaxes, my example would've been:

    match s {
      "^=" => |a, b| try { a.as_str()?.starts_with(b.as_str()?) }.unwrap_or(false),
      "$=" => |a, b| try { Some(a.as_str()?.ends_with(b.as_str()?)) }.unwrap_or(false),
      // original
      "$=" => |a, b| {
        a.as_str()
          .and_then(|a| b.as_str().map(|b| (a, b)))
          .map(|(a, b)| a.starts_with(b))
          .unwrap_or(false)
        },
    }
    

    I think that, contextually, whether or not the return type is Option or Result is pretty clear, and moreso, it doesn't really matter (as far as code comprehension goes). Transparently, the meaning is clear: "I need to check if these two things are valid and do an operation on them." If I had to pick one of these, I would go with the first, because I don't believe that there is any loss of understanding when you consider that this function is embedded in a larger context, as try will always be.

    When I first started looking at this thread, I was against Ok wrapping because I thought it would be better to be explicit, but since then, I've started paying attention to the times that I said "I wish I could use a try block here" and I've come to the conclusion that Ok-wrapping is good.

    I originally thought that not Ok wrapping would be better in the case where your last statement is a function which returns the type which implements Try, but the difference in syntax would be

    try {
      fallible_fn()
    }
    
    try {
      fallible_fn()?
    }
    

    And in this case, I again think Ok-wrapping is better because it makes it clear that fallible_fn is a Try returning function, so it's actually more explicit.

    I want to know what the opposition thinks of this, and, since I can't see many others in this thread, @joshtriplett.

    EDIT: I should mention I was only looking at this from an ergonomics/reading comprehension perspective. I have no idea if one has more technical merits than the other in terms of implementation, such as easier inference.

    Ashkan Kiani at 2019-06-28 00:20:17

  262. I wanted to give try a shot for some nested Option parsing as well:

    #![feature(try_blocks)]
    
    struct Config {
        log: Option<LogConfig>,
    }
    
    struct LogConfig {
        level: Option<String>,
    }
    
    fn example(config: &Config) {
        let x: &str = try { config.log?.level? }.unwrap_or("foo");
    }
    

    This fails with

    error[E0282]: type annotations needed
      --> src/lib.rs:12:19
       |
    12 |     let x: &str = try { config.log?.level? }.unwrap_or("foo");
       |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type
       |
       = note: type must be known at this point
    

    The closest I got was

    fn example(config: &Config) {
        let x: Option<&str> = try { &**config.log.as_ref()?.level.as_ref()? };
        let x = x.unwrap_or("foo");
    }
    

    The as_ref is quite unfortunate. I know that Option::deref will help some here, but not enough. This feels like somehow match ergonomics (or related idea) should come into play.

    The multiple lines is also unfortunate.

    Jake Goulding at 2019-07-02 17:28:16

  263. Could try use an inference fallback of Result like integer literals? Would that let @shepmaster's first attempt infer Result<&str, NoneError>? What remaining issues would there be- probably finding a common error type for ?s to convert to? (Have I missed discussion of this somewhere?)

    Russell Johnston at 2019-07-02 17:43:40

  264. @shepmaster I agree with the type inference. Funnily enough, though, I tried your exact code with a somewhat naive try_ implementation and it works fine: https://github.com/norcalli/koption_macros/blob/4362fba8fa9b6c62fdaef4df30060234381141e7/src/lib.rs#L23

        let x = try_! { config.log?.level? }.unwrap_or("foo".to_owned());
        assert_eq!(x, "debug");
    

    works just fine.

    Ashkan Kiani at 2019-07-10 02:41:19

  265. a somewhat naive try_ implementation

    Yes, but your macro invocation returns a String, not a &str, requiring ownership. You don't show the surrounding code, but this will fail because we don't have ownership of the Config:

    fn example(config: &Config) {
        let x = try_! { config.log?.level? }.unwrap_or_else(|| String::from("foo"));
    }
    
    error[E0507]: cannot move out of captured variable in an `Fn` closure
      --> src/lib.rs:20:21
       |
    19 | fn example(config: &Config) {
       |            ------ captured outer variable
    20 |     let x = try_! { config.log?.level? }.unwrap_or_else(|| String::from("foo"));
       |                     ^^^^^^^^^^ cannot move out of captured variable in an `Fn` closure
    

    It also unconditionally allocates a String; I used unwrap_or_else in this example to avoid that inefficiency.

    Jake Goulding at 2019-07-10 02:54:37

  266. It is a shame this feature was not stabilized before async/await blocks. IMO it would have been more consistent to have

    let fut = async try {
        fut1().await?;
        fut2().await?;
        Ok(())
    };
    

    instead of allowing it to work without the try. But I suppose that ship has long since sailed.

    Re: auto-wrapping, I don't think it will be possible to be consistent with async blocks now. async blocks do "auto-wrapping" of a sort, into an anonymous type implementing Future. But this is true even with early returns, which would not be possible with try blocks.

    It could become doubly confusing if we ever do have a hypothetical async try block. Should that auto-wrap the result?

    jesskfullwood at 2019-08-29 22:57:54

  267. I don't think we've lost any chances at consistency in the auto-wrapping sense. Both async blocks and functions can use ?, and both must do their own manual Ok-wrapping, for both early and final returns.

    A try block, on the other hand, could use ? with automatic Ok-wrapping, including for "early returns" assuming an early return feature- perhaps label-break-value. A hypothetical try function could easily do automatic Ok-wrapping on both early and final returns.

    A hypothetical async try block could simply combine the functionality of the two- auto-Ok-wrap, and then auto-Future-wrap. (The other way around is impossible to implement, and would arguably be written try async anyway.)

    The inconsistency I do see is that we've conflated async blocks with functions. (This happened at the last minute contrary to the RFC, no less.) What this means is that return in async blocks exits the block, while return in try blocks exits the containing function. However, these do at least make sense in isolation, and async blocks without early-return or label-break-value would be much harder to use.

    Russell Johnston at 2019-08-30 00:18:23

  268. Anything stopping stabilization of this, or just nobody has taken the time to do it yet? I'm interested in creating the necessary PRs otherwise 🙂

    boyned//Kampfkarren at 2019-11-18 10:02:31

  269. On November 18, 2019 2:03:36 AM PST, Kampfkarren notifications@github.com wrote:

    Anything stopping stabilization of this, or just nobody has taken the time to do it yet? I'm interested in creating the necessary PRs otherwise 🙂 >

    -- > You are receiving this because you were mentioned.> Reply to this email directly or view it on GitHub:> https://github.com/rust-lang/rust/issues/31436#issuecomment-554944079

    Yes, the blocker to stabilization is working through the decision on Ok-wrapping. This shouldn't be stabilized until we have consensus on how it should behave.

    Josh Triplett at 2019-11-18 16:28:58

  270. I'm personally against Ok-wrapping, but I am wondering how hard it would be to add after-the-fact. Is no-ok-wrapping forward compatible with ok-wrapping?

    I can imagine tricky cases such as the ambiguity of Result<Result<T,E>>, but in such ambiguous cases, we could just fall back to no-wrapping. The user could then explicitly Ok-wrap to disambiguate. That doesn't seem too bad, since I don't expect this sort ambiguity to come up too often...

    mark at 2019-11-18 16:49:46

  271. There should be no ambiguity at all, because it's not Ok-coercion but Ok-wrapping. try { ...; x } would yield Ok(x) just as unambiguously as Ok({ ...; x }).

    Russell Johnston at 2019-11-18 17:40:21

  272. @joshtriplett Is this unresolved? The tracking issue has resolve whether catch blocks should "wrap" result value as checked off, citing https://github.com/rust-lang/rust/issues/41414

    boyned//Kampfkarren at 2019-11-18 22:40:24

  273. @rpjohnst Sorry, I should have been more clear. What I mean is that if we stabilized try now without Ok-wrapping, I believe it could be added later backwards-compatibly.

    That is, I think most people agree that we should have try blocks, but not everyone agrees about catch or ok-wrapping. But I don't think those discussions need to block try...

    mark at 2019-11-19 00:31:38

  274. @Kampfkarren Yes. The above conversation details the progression of this matter. It was prematurely ticked off without fully consulting everyone. @joshtriplett in particular had concerns, which several others (including myself) shared.

    Alexander Regueiro at 2019-11-19 00:47:08

  275. @mark-i-m How exactly do you see Ok-wrapping being added in the future? I'm trying to figure out how that might be done, and I can't quite see it.

    Gabriel Smith at 2019-11-19 03:10:39

  276. So I will preface this by saying I don't know if it's a good idea or not...

    We would stabilize try block without ok-wrapping. For example:

    let x: Result<usize, E> = try { 3 }; // Error: expected Result, found usize
    let x: Result<usize, E> = try { Ok(3) }; // Ok (no pun intended)
    

    Later, suppose that we came to consensus that we should have Ok-wrapping, then we could allow some cases that didn't previously work before:

    let x: Result<usize, E> = try { 3 }; // Ok
    let x: Result<usize, E> = try { Ok(3) }; // Also Ok for backwards compat
    let x: Result<Result<usize, E1>, E2> = try { Ok(3) }; // Ok(Ok(3))
    let x: Result<Result<usize, E1>, E2> = try { Ok(Ok(3)) }; // Ok(Ok(3))
    

    The question is whether this can cause something to become ambiguous that wasn't before. For example:

    let x = try { Err(3) }; // If x: Result<Result<T1, usize>, usize>, then it is not clear if user meant Ok(Err(3)) or Err(3)...
    

    Although, maybe Ok-wrapping already has to grapple with this problem?

    Anyway, my intuition is that such weird cases don't come up that often, so it may not matter that much.

    mark at 2019-11-19 03:49:42

  277. What about using Ok-wrapping except where the returned type is Result or Option? That would allow simpler code in most cases but would allow specifying the exact value where needed.

    // Ok-wrapped
    let v: Result<i32, _> = try { 1 };
    
    // not Ok-wrapped since the returned type is Result
    let v: Result<i32, _> = try { Ok(1) };
    
    // not Ok-wrapped since the returned type is Result
    let v: Result<i32, _> = try { Err("error") };
    
    // Ok-wrapped
    let v: Option<i32> = try { 1 };
    
    // not Ok-wrapped since the returned type is Option
    let v: Option<i32> = try { Some(1) };
    
    // not Ok-wrapped since the returned type is Option
    let v: Option<i32> = try { None };
    

    Jacob Lifshay at 2019-11-19 06:03:52

  278. Adding Ok-coercion or some kind of syntax dependent Ok-wrapping (which is what would need to happen to support stabilising without it and introducing it later on) would be very bad for readability, and has been broadly argued against multiple times on i.rl.o (commonly by people misunderstanding the straightforward Ok-wrapping that is implemented).

    I’m personally strongly in favour of Ok-wrapping as implemented, but would even more strongly be against any form of coercion or syntax dependence that makes understanding the situations which will wrap difficult (I would take having to write useless Ok(...)’s everywhere over having to try and figure out if it has coerced or not).

    let x = try { Err(3) }; // If x: Result<Result<T1, usize>, usize>, then it is not clear if user meant Ok(Err(3)) or Err(3)...
    

    Although, maybe Ok-wrapping already has to grapple with this problem?

    Nope, that is unambiguously Ok(Err(3)), Ok-wrapping is independent of syntax or types, it just wraps whatever the output of the block is in the Try::Ok variant.

    Nemo157 at 2019-11-19 11:06:50

  279. @mark-i-m I don't think we can reasonably move from one to the other after stabilization. As bad as I consider Ok-wrapping to be, inconsistent Ok-wrapping that tries to guess whether you want it or not would be even worse.

    Josh Triplett at 2019-11-19 11:54:23

  280. In my codebase I'm dealing with a lot of optional values, so I introduced my own try block like macro a long time ago. And back when I was introducing it I had various variants with and without Ok-wrapping, and the Ok-wrapping version turned out to be so much more ergonomic, that it is the sole macro that I ended up using.

    I have a ton of optional values that I need to work with, and they are mostly numeric, so I have tons of situations like this:

    let c = try { 2 * a? + b? };
    

    Without Ok-wrapping this would be much less ergonomic to the point where I would likely stay on my own macro than using the real try blocks.

    Christopher Serr at 2019-11-19 12:13:35

  281. Given the venerable history of this tracking issue, its original and regrettable conflation with the ? operator, and the roadblock over the Ok-wrapping issue, I'd suggest closing this issue outright and sending try back to the beginning of the RFC process, where this discussion can get the visibility it deserves and (hopefully) reach some sort of conclusion.

    bstrie at 2019-11-22 19:10:30

  282. Without Ok-wrapping this would be much less ergonomic

    Could you please elaborate on what exactly non-ergonomic stuff it would introduce?

    Without Ok-wrapping, your example would look like:

    let c = try { Ok(2 * a? + b?) };
    

    which is pretty good in my opinion.

    I mean, with a small example like this it might look like overkill but the more code the try block contains the less impact this Ok(...) wrapper causes.

    CreepySkeleton at 2019-12-04 00:54:58

  283. Further to @CreepySkeleton's comment, it should be noted that it is very easy to create a macro that emulates Ok-wrapping if the try block does not do it (and someone will surely create a standard crate for this tiny macro), but the converse is not so.

    Alexander Regueiro at 2019-12-04 16:18:17

  284. That macro is not possible while the Try trait is unstable.

    Nemo157 at 2019-12-04 16:41:43

  285. Why? Anyway, when it does stabilise (hypothetically not too far in the future), it will be very much possible.

    Alexander Regueiro at 2019-12-04 23:02:32

  286. @Nemo157 try blocks are also on nightly only right now, and they likely won't be stabilized in the unlikely case that we decide to rip out Try. This means that they likely won't be stabilized before Try. So, saying the macro isn't possible doesn't makes sense.

    RustyYato at 2019-12-04 23:59:54

  287. @KrishnaSannasi I'm curious why Try might be ripped out?

    mark at 2019-12-05 01:45:24

  288. @mark-i-m I don't think it will, I am just explaining why worrying about Try being on nightly wrt to try blocks is not a realistic concern. I am looking forward to Try on stable.

    RustyYato at 2019-12-05 02:26:22

  289. Given that ? has been stabilized already, and try blocks have a clear design encompassing both Result and Option in the same way that ? does, there's no reason I can see to block stabilizing them on stabilizing Try. I haven't been keeping a close eye on it, but my impressions were that there was much less consensus on the design of Try than for try blocks, so I could see try blocks stabilizing years before the Try trait (as has happened for ?). And even if the Try trait is abandoned I see no reason that should block try blocks being stabilized as working with just Result and Option like ? would then be.

    (For why you couldn't write that macro given stabilized try blocks and an unstable Try trait, the macro would have expand to try { Try::from_ok($expr) }; you could create per-type macros for just Result and Option, but IMO that would not meet the "very easy to [...] emulate" point).

    Nemo157 at 2019-12-05 09:10:44

  290. Given ? is already special-cased stable even though the Try trait can't be used on stable, I don't see why Try being unstable would block try blocks being implemented on stable, because if Try trait is removed we still have Option and Result supporting ? on stable, just without the ergonomics.

    Brendan Molloy at 2019-12-13 13:56:05

  291. I would suggest the following concept for try catch semantic ...

    Consider the following code:

    union SomeFunctionMultipleError {
        err0: Error1,
        err1: Error2,
    }
    
    struct SomeFunctionFnError {
        index: u32,
        errors: SomeFunctionMultipleError,
    }
    
    fn some_function() -> Result<i32, SomeFunctionFnError> {
        if 0 == 0 {
            Ok(2)
        } else {
            Err(SomeFunctionFnError{ index: 0, errors: SomeFunctionMultipleError {err1: Error2 {id0: 0, id1: 0, id3: 0}}})
        }
    }
    
    union OtherFunctionMultipleError {
        err0: Error1,
        err1: Error2,
        err2: Error3,
    }
    
    struct OtherFunctionFnError {
        id: u32,
        errors: OtherFunctionMultipleError,
    }
    
    fn other_function() -> Result<i32, OtherFunctionFnError> {
        if 0 == 0 {
            Ok(2)
        } else {
            Err(OtherFunctionFnError {id: 0, errors: OtherFunctionMultipleError {err0: Error1 {id0: 0, id1: 0}}})
        }
    }
    

    This is the code that could be generated by Zero-Overhead exceptions in Rust with following syntax feature:

    fn some_function() -> i32 throws Error1, Error2 {
        if 0 == 0 {
            2
        } else {
            Error2 {id0: 0, id1: 0, id3: 0}.throw
        }
    }
    
    fn other_function() -> i32 throws Error1, Error2, Error3 {
        if 0 == 0 {
            2
        } else {
            Error1{id0: 0, id1: 0}.throw
        }
    }
    

    or even these errors could be deduced by compiler implicitly:

    fn some_function(i: i32) -> i32 throws { // Implicitly throws Error1, Error2
        if i == 0 {
            2
        } else if i == 1 {
            Error1 {id0: 0, id1: 0, id3: 0}.throw
        } else {
            Error2 {id0: 0, id1: 0, id3: 0}.throw
        }
    }
    
    fn other_function(i: i32) -> i32 throws { // Implicitly throws Error1
        if i == 0 {
            2
        } else {
            Error1{id0: 0, id1: 0}.throw
        }
    }
    

    This nothing else syntactic sugar !! Behavior is the same !!

    Denis at 2019-12-24 09:05:36

  292. Hi all,

    Have anybody view my proposal regarding Zero-overhead exceptions that is above ?

    Denis at 2020-01-22 22:01:59

  293. @redradist One of the main points of try block is that we could use it inside of a function, with no need to create a function for every block. Your proposal is entirely unrelated here as far as I see it.

    CreepySkeleton at 2020-01-23 00:28:10

  294. Just today I felt the need for try blocks. I have a big function that has many ? operations. I wanted to add context to the errors, but doing so for each ? would've needed a huge amount of boilerplate. Catching the errors with try and adding the context in one place would have prevented this.

    Btw. wrapping the operations in an inner function would have been hard in this case, because the context has not obvious lifetime, and dividing stuff into multiple functions breaks NLL.

    Pyry Kontio at 2020-01-29 19:51:20

  295. I would like to mention that in the current implementation a try block is not an expression. Think that this is an oversight.

    Sebastian Malton at 2020-01-30 21:11:05

  296. I would like to mention that in the current implementation a try block is not an expression. Think that this is an oversight.

    Could you post the code that isn't working for you? I'm able to use it in an expression context here: (Rust Playground)

    #![feature(try_blocks)]
    
    fn main() {
        let s: Result<(), ()> = try { () };
    }
    

    Sunjay Varma at 2020-01-30 21:13:59

  297. Sure, here it is.

    Sebastian Malton at 2020-01-30 21:19:19

  298. And here is another that shows that type inference on try blocks is not yet complete. Which is especially annoying since type ascriptions are not supported in if let blocks.

    Sebastian Malton at 2020-01-30 21:21:10

  299. @Nokel81 the problem with your former example is that the expression in if let $pat = $expr is not a regular expression context, but rather a special "no brackets expression" context. For an example of how this works with struct expressions, see this example where it is syntactically clear that there is a struct expression there, and this example where it is not. So the error is not that try isn't an expression, but that the error is wrong, and should say "try expression is not allowed here; try surrounding it with parenthesis" (and the incorrect warning about unnecessary parenthesis suppressed).

    Your latter example is actually ambiguous for type inference. The type of e is _: From<usize> in this case, which is not enough information to give it a concrete type. You would need to use it some manner to give it a concrete type to allow type inference to succeed. This is not an issue specific to try; it's how type inference works in Rust.

    Now, if you immediately try to match as an Ok and discard the Err case, you have a case for a suboptimal error message with no real simple way to address it.

    Crystal Durham at 2020-02-17 21:36:49

  300. Ah thank you very much for the in-depth explanation. I guess I am still confused why the later to ambiguous for type inference. Why is the type of the expression not Result<isize, usize>?

    Sebastian Malton at 2020-02-19 17:59:51

  301. The ? operator can perform type conversions between different error types using the From trait. It expands roughly to the following rust code (ignoring the Try trait):

    match expr {
        Ok(v) => v,
        Err(e) => return From::from(e),
    }
    

    The From call uses the type of the final returned expression to determine what error type conversions to do, and won't default to the type of the passed-in value automatically.

    Nika Layzell at 2020-03-09 15:16:55

  302. Apologies if this has been addressed already, but it seems strange to me that:

    #![feature(try_blocks)]
    
    fn main() -> Result<(), ()> {
        let result = try { // no type annotation
            Err(())?;
        };
        result.map_err(|err| err)
    }
    

    fails to compile with:

    error[E0282]: type annotations needed
    

    but:

    #![feature(try_blocks)]
    
    fn main() -> Result<(), ()> {
        let result : Result<_, _> = try { // partial type annotation
            Err(())?;
        };
        result.map_err(|err| err)
    }
    

    is okay.

    If this were a problem because the type arguments of Result could not be deduced I'd understand, but, as shown above, that's not the case and rustc is able to perform inference once it is told that the result of a try expression is some sort of Result, which it ought to be able to infer from core::ops::Try::into_result.

    Thoughts?

    Nathan Sharp at 2020-03-24 01:54:58

  303. @nwsharp that's because try/? is generic over Try types. If you had some other type that was impl Try<Ok=_, Error=()>, the try block could evaluate to that type as well as Result. Desugared, your example is roughly

    #![feature(try_trait, label_break_value)]
    
    use std::ops::Try;
    
    fn main() -> Result<(), ()> {
        let result /*: Result<_, _>*/ = 'block: {
            match Try::into_result(Err(())) {
                Ok(ok) => Try::from_ok(ok),
                Err(err) => {
                    break 'block Try::from_error(From::from(err));
                }
            }
        };
        result.map_err(|err| err)
    }
    

    Crystal Durham at 2020-03-24 02:06:11

  304. @CAD97 Thank you for the explanation.

    That said, I didn't expect that try would effectively be capable of causing a sort-of conversion between different Try impls.

    I'd expect a de-surgaring where the same Try impl is selected for into_result, from_ok, and from_error.

    In my opinion, the ergonomic loss of being unable to perform the type inference (especially considering that no alternate impl Try even exists), doesn't outweigh the benefit of allowing this conversion.

    We could permit inference by removing the ambiguity, and keep ability to opt-in to the conversion via something like:

    try { ... }.into()
    

    With the corresponding blanket impl:

    impl<T: Try, E: Into<T::Err>> From<Result<T::Ok, E>> for T {
        fn from(result: Result<T::Ok, E>) -> Self {
            match result {
                Ok(ok) => T::from_ok(ok),
                Err(err) => T::from_err(err.into()),
            }
        }
    }
    

    (Which honestly I suppose makes sense having regardless, though I do personally doubt the automatic conversion of error types here. If it's wanted, the user should .map_err() on the Result.)

    In general, I think that this de-sugaring is "too clever". It hides too much and its current semantics are liable to confuse people. (Especially considering that the current implementation asks for type annotations on something that doesn't support them directly!)

    Nathan Sharp at 2020-03-24 04:15:36

  305. Or, going even further with the blanket impl, I suppose.

    impl <T: Try, U: Try> From<U> for T 
        where U::Ok : Into<T::Ok>, U::Err : Into<T::Err>
    {
        fn from(other: U) -> Self {
            match other.into_result() {
                Ok(ok) => Self::from_ok(ok.into()),
                Err(err) => Self::from_err(err.into()),
            }
        }
    }
    

    Or whatever...

    Nathan Sharp at 2020-03-24 04:20:38

  306. That said, I didn't expect that try would effectively be capable of causing a sort-of conversion between different Try impls.

    I'd expect a de-surgaring where the same Try impl is selected for into_result, from_ok, and from_error.

    In my opinion, the ergonomic loss of being unable to perform the type inference (especially considering that no alternate impl Try even exists), doesn't outweigh the benefit of allowing this conversion.

    There are four stable Try types: Option<T>, Result<T, E>, Poll<Result<T, E>>, and Poll<Option<Result<T, E>>.

    NoneError is unstable, so Option<T> is stuck trying in Option<T> while NoneError is unstable. (Note though that the docs explicitly call out From<NoneError> as "enable option? to your error type.")

    The Poll impls, however, set their error type to E. Because of this, the "type morphing" of Try is stable, because you can ? a Poll<Result<T, E>> in a -> Result<_, E> to get a Poll<T> and early return the E case.

    In fact, this powers a "cute" little helper:

    fn lift_err<T, E>(x: Poll<Result<T, E>>) -> Result<Poll<T>, E> { Ok(x?) }
    

    Crystal Durham at 2020-03-24 04:47:39

  307. @CAD97 Thanks for humoring me. This will be a tricky thing to teach to newcomers and will require quite a bit of love in terms of error messages.

    Has thought been given to allowing the specification of the desired impl Try to assuage the unintuitive behavior here?

    For example, bikeshedding for a bit, try<T> { ... }. Or is there yet again something to trip up on with even that?

    Nathan Sharp at 2020-03-24 05:29:14

  308. To maybe add a bit more color here, the fact that try { } over a bunch of Result's doesn't "just" produce a Result is unexpected and makes me sad. I understand why, but I don't like it.

    Nathan Sharp at 2020-03-24 05:40:02

  309. Yes, there has been discussion of the combination of "generalized type ascription" (there's your term to search for) and try. I think, last I heard, try: Result<_, _> { .. } was intended to work eventually.

    But I do agree with you: try blocks deserve some targeted diagnostics for making sure their output type is specified.

    Crystal Durham at 2020-03-24 05:42:08

  310. Please see this separate issue for a specific narrow question to resolve language team consensus on the matter of Ok-wrapping.

    Please read the opening comment of that thread before commenting, and in particular, please note that that thread is only about that one question, not any other issue related to try or ? or Try.

    Josh Triplett at 2020-04-09 01:50:56

  311. I don't see why the try block is needed. This syntax

    fn main() -> Result<(), ()> {
        try {
            if foo() {
                Err(())?
            }
            ()
        }
    }
    

    can be replaced with this:

    fn main() -> Result<(), ()> {
        Ok({
            if foo() {
                Err(())?
            }
            ()
        })
    }
    

    They both use the same number of characters, but the second is already stable.

    When assigning the result to a variable, that might indicate a helper function should be created to return the result. If that's not possible, a closure can be used instead.

    dylni at 2020-04-11 14:54:36

  312. @dylni try blocks are especially useful when they don’t contain the entire body of a function. The ? operator on error makes flow control go to the end of the inner-most try block, without returning from the function.

    fn main() /* no result here */ {
        let result  = try {
            foo()?.bar()?.baz()?
        };
        match result {
            // …
        }
    }
    

    Simon Sapin at 2020-04-11 14:59:00

  313. @SimonSapin Does that come up that often? I've rarely had a situation it would make sense for, and there's usually a good way to get around it. In your example:

    fn main() /* no result here */ {
        let result  = foo()
            .and_then(|x| x.bar())
            .and_then(|x| x.baz());
        match result {
            // …
        }
    }
    

    That's more verbose, but I think a simpler solution would be a method closure syntax:

    fn main() /* no result here */ {
        let result  = foo()
            .and_then(::bar)
            .and_then(::baz);
        match result {
            // …
        }
    }
    

    The type is also correctly inferred with and_then, where you need type annotations for try. I've had this come up infrequently enough that I don't think a terser syntax would be worth the readability harm.

    dylni at 2020-04-11 15:11:51

  314. The accepted RFC has some more reasoning: https://rust-lang.github.io/rfcs/0243-trait-based-exception-handling.html

    Anyway the arguments in favor of language constructs for control flow with the ? (and .await) operator over chaining methods like and_then have already been discussed extensively.

    Simon Sapin at 2020-04-11 15:23:59

  315. Anyway the arguments in favor of language constructs for control flow with the ? (and .await) operator over chaining methods like and_then have already been discussed extensively.

    @SimonSapin Thanks. This and re-reading the RFC convinced me this can be useful.

    dylni at 2020-04-11 15:46:33

  316. I thought I could use try blocks to easily add context to errors, but no luck so far.

    I wrote a small function that works fine. Notice that File::open()? fails with a std::io::Error while the following line fails with an anyhow::Error. Despite the differing types, the compiler figures out how to convert both to a Result<_, anyhow::Error>.

    fn tls_add_cert(config: &ClientConfig, path: impl AsRef<Path>) -> Result<(usize, usize), anyhow::Error> {
        let path = path.as_ref();
        let mut file = BufReader::new(File::open(path)?);
        Ok(config.root_store.add_pem_file(&mut file)
            .map_err(|_| anyhow!("Bad PEM file"))?)
    }
    

    I wanted to add some error context so I tried to use a try-block and anyhow's with_context():

    fn tls_add_cert(config: &ClientConfig, path: impl AsRef<Path>) -> anyhow::Result<(usize, usize)> {
        let path = path.as_ref();
        try {
            let mut file = BufReader::new(File::open(path)?);
            Ok(config.root_store.add_pem_file(&mut file)
                .map_err(|_| anyhow!("Bad PEM file"))?)
        }
        .with_context(|| format!("Error adding certificate {}", path.display()))
    }
    

    But now type inference fails:

    error[E0282]: type annotations needed
      --> src/net.rs:29:5
       |
    29 | /     try {
    30 | |         let mut file = BufReader::new(File::open(path)?);
    31 | |         Ok(config.root_store.add_pem_file(&mut file)
    32 | |             .map_err(|_| anyhow!("Bad PEM file"))?)
    33 | |     }
       | |_____^ cannot infer type
       |
       = note: type must be known at this point
    

    I don't understand why a type annotation is needed here but not in the first case. Nor do I see any easy way to add one, as opposed to using an IIFE which does let me add an annotation:

    (|| -> Result<_, anyhow::Error> {
        let domain = DNSNameRef::try_from_ascii_str(host)?;
        let tcp = TcpStream::connect(&(host, port)).await?;
    
        Ok(tls.connect(domain, tcp).await?)
    })()
    .with_context(|| format!("Error connecting to {}:{}", host, port))
    

    John Kugelman at 2020-04-15 20:08:42

  317. @jkugelman

    Again,

    that's because try/? is generic over Try types. If you had some other type that was [impl Try<Ok=_, Error=anyhow::Error>], the try block could evaluate to that type as well as Result.

    (Also, you don't need to Ok your trailing expression in a try block (#70941).)

    I think the fact that this continues to show up means that

    • Before stabilization, try needs to support a type ascription (try: Result<_,_> { or whatever) or otherwise mitigate this issue,
    • This definitely needs targeted diagnostics for when type inference of a try block fails, and
    • We should strongly consider giving try a type fallback to Result<_,_> when it's not otherwise constrained. Yes, that's difficult, under-specified, and potentially problematic, but it would solve the 80% case of try blocks needed a type annotation because of $12: Try<Ok=$5, Error=$8> not being specific enough.

    Additionally, given #70941 seems to be resolving towards "yes, we want (some form of) 'Try::from_ok wrapping'", we probably also want a targeted diagnostic for when the tail expression of a try block is returning Ok(x) when x would work.

    Crystal Durham at 2020-04-15 20:28:43

  318. I suspect that the right behavior for try is

    • extend the syntax to permit a manual ascription like try: Result<_, _> { .. }, try as Result<>, or whatever (I think try: Result is probably fine? it seems to be the preferred syntax)
    • examine the "expected type" that comes from context -- if one is present, prefer that as the result type of a try
    • otherwise, default to Result<_, _> -- this is not type inference fallback like with i32, it would happen earlier, but that would mean that things like try { }.with_context(...) compile.

    However, I am concerned that we may get errors around ? and the into coercion, at least so long as the error type is not specified. In particular if you write code where you ? the result of a try block, like so:

    #![feature(try_blocks)]
    
    use std::error::Error;
    fn foo() -> Result<(), Box<dyn Error>> {
        let x: Result<_, _> = try {
            std::fs::File::open("foo")?;
        };
        
        x?;
        
        Ok(())
    }
    
    fn main() { 
    }
    

    You still get errors (playground) and rightly so, because it's not clear on which ? the "into" coercion should trigger.

    I'm not sure what's the best solution here but it probably involves some type inference fallback that will make me nervous.

    Niko Matsakis at 2020-04-16 15:49:24

  319. probably involves some type inference fallback that will make me nervous.

    Simplest one: if all uses of ? in a given Try block contain the same Try::Error type, use that error type for the containing try block (unless otherwise bound).

    The "(unless otherwise bound)" is, of course, the subtle scary part.

    Crystal Durham at 2020-04-18 00:38:42

  320. I hope I'm not being too unconstructive with this post. However, I wanted to contrast @nikomatsakis example with one from a parallel world where ? doesn't do a forced conversion, and there is no auto-wrapping of the try block result:

    use std::error::Error;
    fn foo() -> Result<(), Box<dyn Error>> {
        let x = try {
            std::fs::File::open("foo").err_convert()?;
            Ok(())
        };
        
        x?;
        
        Ok(())
    }
    

    In this world:

    • It's easy to see that both the try scope and the fn itself result in success without any value. It's also easy to see without even trying that they produce Results.
    • It's obvious where error conversion happens.
    • The error conversion could be moved to the x? expression, making the try scope specific to the std::fs::File operations.
    • All type hints chain fluently from the type signature. Both for the machine and for us humans.
    • Type hinting by the user is only required in cases where we actually want to fold errors into another, independent one.

    I would be very happy in that parallel universe.

    Robert »phaylon« Sedlacek at 2020-04-18 13:03:43

  321. @phaylon While I appreciate the careful way you wrote that comment, I'm afraid it is rather unconstructive. Error conversion is part of ? and that is not going to change, and, in light of that, ok-wrapping is basically orthogonal from the rest of this discussion.

    Niko Matsakis at 2020-04-20 14:11:00

  322. If try functions (with return and throw types) are ever to be considered, then maybe it would be worth to also consider the syntax for ascribing try block to be something similar.

    e.g.

    try fn foo() -> u32 throw String {
      let result = try: u32 throw String {
        123
      };
      result?
    }
    

    Nikita at 2020-04-20 14:30:32

  323. Sorry if this has been discussed but what are the advantages of using

    try fn foo() -> u32 throw String { ... }
    

    or similar as opposed to

    fn foo() -> Result<u32, String> { ... }
    

    ? Just seems like duplicate syntax.

    Pietro Gorilskij at 2020-04-23 21:06:20

  324. @gorilskij As I understand it, the primary advantage is to get Ok-wrapping. Otherwise, you have to write:

    fn foo() -> Result<u32, String> {
        try {
            // function body
        }
    }
    

    Jon Gjengset at 2020-04-23 21:14:59

  325. Some people also prefer throws as they find the exceptions terminology relatable.

    Personally, I want to stay as far as possible from the appearance of exceptions.

    mark at 2020-04-24 03:19:00

  326. This is not the thread to discuss try fn, so please don't take this tangent further. This thread is for the accepted feature of try blocks, not the potential (and as of yet, not RFCd) feature try fn.

    Crystal Durham at 2020-04-24 03:30:52

  327. I just noticed that the original RFC for ? proposed using Into, not From. It states:

    The present RFC uses the std::convert::Into trait for this purpose (which has a blanket impl forwarding from From).

    Though leaves the exact upcasting method as an unresolved question. Into was (presumably) preferred based on the guidance from From:

    Prefer using Into over using From when specifying trait bounds on a generic function. This way, types that directly implement Into can be used as arguments as well.

    However, in the Try trait RFC, Into is no longer mentioned, and the conversion is done using From instead. This is also what the code now uses, even for ? https://github.com/rust-lang/rust/blob/b613c989594f1cbf0d4af1a7a153786cca7792c8/src/librustc_ast_lowering/expr.rs#L1232

    This seems unfortunate, as there is no blanket implementation going from Into to From. This means that errors that implement Into (as opposed to From) either in error, for legacy reasons, or out of some other need, cannot be used with ? (or Try). It also means that any implementation that follows the recommendation in the standard library to use Into in bounds cannot use ?. With an example, the standard library recommends I write:

    fn with_user_err<E>(op: impl Fn() -> Result<(), E>) -> Result<(), MyError>
    where E: Into<MyError>
    

    but if I do, I cannot use ? in the function body. If I want to do that, I have to write

    fn with_user_err<E>(op: impl Fn() -> Result<(), E>) -> Result<(), MyError>
    where MyError: From<E>
    

    but if I do, users with error types that implement Into instead of From cannot use this function. Note that the inverse is not true, due to the blanket impl of Into based on From.

    It is probably (?) too late to fix ? now (which is very unfortunate — maybe in the next edition?), but we should at least make sure to not dig deeper down that path in the Try trait.

    Jon Gjengset at 2020-04-25 19:11:29

  328. @jonhoo @cuviper tried to change the desugaring from From to Into in #60796 to check #38751 and it resulted in a large amount of inference breakage exactly because of the From -> Into blanket impl made it harder for rustc to handle the common case of the identity conversion. It was decided that it wasn't worth the cost breaking inference that much.

    RustyYato at 2020-04-25 19:30:47

  329. @jonhoo you might also find this comment from niko informative:

    There is also a hard-coded limit in the trait system. If you have to solve a goal like ?X: Into<ReturnType>, we will fail to resolve, but if you have to solve a goal like ReturnType: From<?X>, we will potentially succeed and infer a value for ?X.

    Edit: Here, ?X refers to some unknown inference variable. The hard-coded limit in today's trait system is that the Self type must be at least partly inferred for us to explore that option.

    The TL;DR is that inferring with Into is harder, and in an inherent way to the way in which the trait solver functions.

    Crystal Durham at 2020-04-25 19:47:06

  330. @KrishnaSannasi @CAD97 Thank you, that's helpful! I still worry about us stabilizing too much that's based on From given that we are leaving implementors of Into sort of permanently out. Is the expectation that the inference here may eventually get better? Should the guidance to prefer Into in bounds be changed? Are we expecting that with the new trait coherence rules in 1.41 (I think it was) there is no longer a reason to implement only Into, and consider all of those impls bugs?

    Jon Gjengset at 2020-04-25 19:58:34

  331. If inference get's good enough, it should be forward compatible to change to Into later. The worst that we could break is inference given that From implies Into

    RustyYato at 2020-04-26 01:19:51

  332. Does this (the Try trait) cover allowing ? to work with the Into/From traits with generic bounds for types which implement Try (e.g. Result itself)?

    i.e. ? in a closure or function which returns e.g. impl Into<Result>

    (It doesn't seem to when I try that on nightly?)

    Jeremiah Senkpiel at 2020-05-25 23:30:41

  333. I'm keen to see this stabilised. Having read this thread, and #70941, I think the summary should be updated as follows:

    1. "resolve whether catch blocks should "wrap" result value" should be ticked, "Resolved as yes"

    2. New concern added about these inference problems. Perhaps something like:

      • [ ] Ergonomic difficulties due to problems with type inference.

    ISTM that this last concern could be addressed by, amongst other ways:

    • Add a bespoke type ascryption syntax to try, before stabilisation
    • Decide that https://github.com/rust-lang/rfcs/pull/803 #23416 (Type ascription) will address this and should be stabilised first
    • Add some kind of automatic fallback (eg to Result, perhaps as suggested in https://github.com/rust-lang/rust/issues/31436#issuecomment-614735806
    • Decide to stabilise try as-is, now, leaving syntactic/semantic space for future improvement

    (Some of these options are not mutually exclusive.)

    Thanks for your attention and I hope you find this message helpful.

    Ian Jackson at 2020-08-02 13:48:17

  334. Type ascription has a number of issues (syntactic and otherwise), and seems unlikely to get implemented soon, let alone stabilized; blocking try blocks on type ascription syntax doesn't seem appropriate.

    A fallback to Result might help, but doesn't solve the type inference problems with error types: try { expr? }? (or in practice more complex equivalents) have effectively two calls to .into(), which gives the compiler too much flexibility on the intermediate type.

    Josh Triplett at 2020-08-02 18:25:19

  335. @ijackson thanks for taking the initiative to summarize the current state. I think you're correct that there are various ways we could improve on try blocks, but one of the problems is that we're not sure which one to do, in part because each solution has its own unique drawbacks.

    With regard to type ascription, though, I do feel like the implementation challenges there aren't that difficult. That might be a good candidate to put some attention into and try to push it over the finish line regardless. I don't recall whether there was much controversy about the syntax or anything like that.

    Niko Matsakis at 2020-08-05 21:28:50

  336. On Wed, Aug 05, 2020 at 02:29:06PM -0700, Niko Matsakis wrote:

    With regard to type ascription, though, I do feel like the implementation challenges there aren't that difficult. That might be a good candidate to put some attention into and try to push it over the finish line regardless. I don't recall whether there was much controversy about the syntax or anything like that.

    As I recall, the primary concern was that allowing type ascription everywhere would be a substantial grammar change, and potentially a limiting one. I don't recall the full details, just that the concern was raised.

    Josh Triplett at 2020-08-07 05:42:06

  337. Personally I think the ergonomic problems are not so bad that it is not worth stabilising this feature now. Even without expression type ascription, introducing a let binding is not so ugly a workaround.

    Also, try blocks might be useful in macros. In particular, I wonder if @withoutboats excellent fehler library would suffer from fewer problems with deficiencies in our macro system, if it could wrap the bodies of procs in try.

    Ian Jackson at 2020-11-02 18:26:36

  338. I run into places where I would love to use try blocks a lot. It would be good to get this over the line. Personally, I would absolutely, 100% sacrifice type ascription if it were required to get try blocks over the line. I have yet to find myself in a situation where I said "dang I would love to have type ascription here," but end up doing an IIFE to simulate try blocks a lot. Leaving a long-term, useful feature unstable because it conflicts with another long-term, unstable feature is a really unfortunate situation.

    To be slightly more specific, I find myself doing this when I am inside a function that returns Result, but I want to do some sort of processing on things that return an Option. That said, if Try in general were stable, I would still probably prefer try blocks, as I don't actually want to return from the main function to do so, but instead, give some sort of default value if anything in the chain is None. This tends to happen for me in serialization style code.

    Steve Klabnik at 2020-11-06 14:18:24

  339. Personally, I've wanted type ascription far more often than try blocks (though i have wanted both at times). In particular, I've often struggled with "type debugging" where the compiler infers a different type from what i expected. Usually, you have to add a new let binding somewhere, which is really disruptive and causes rustfmt to break the undo history. Moreover, there are lots of places where type ascription would avoid an extra turbo fish.

    In constrast, I can just use and_then or other combinators to terminate early without exiting. Perhaps not as clean aa try blocks, but not that bad either.

    mark at 2020-11-06 14:55:28

  340. @steveklabnik @mark-i-m Try blocks and type ascription are not in any way in conflict, it is not a question of one feature or another. It's just try blocks have unergonomic type inference failures, and generalized type ascription could be a way to solve that problem, but since generalized type ascription is not a near term feature or even a sure thing, @joshtriplett (and I agree) doesn't want this feature to block on generalized type ascription happening.

    This doesn't even mean we wouldn't make generalized type ascription the solution to the problem; one option deserving investigation is "stabilize try as is, expecting that someday generalized type ascription will solve that problem." All that's been said is don't block stabilization on type ascription.


    @rust-lang/lang I have to admit its a bit hard to understand the nuance of the type inference failure from this thread, because of the limitations of GitHub and the many other subjects that are discussed here. Since reaching a decision about how to handle the inference failures is the only thing blocking try blocks from stabilizing, I think it would be beneficial if we had a meeting to discuss this, and someone could take point on getting a deep understanding of the inference issue.

    One question that occurs to me, for example: is this inference issue specifically because of the conversion flexibility we've allowed in Try? I know that decision has been discussed to death, but if that's so this seems like pertinent new information that could justify changing the definition of the Try trait.

    srrrse at 2020-11-06 16:16:27

  341. @withoutboats I agree with the need to collect all the information in one place and the desire to push this feature over the finish line. That said, I think the last time we investigated here it also became clear that changes to Try might be difficult because of backwards compatibility -- @cramertj mentioned some specific Pin impls, IIRC.

    Niko Matsakis at 2020-11-06 17:16:32

  342. Like @steveklabnik, I am constantly doing IIFE to simulate try blocks. Thank you all for considering the prioritization of this feature.

    Jarred Nicholls at 2020-12-04 12:59:49

  343. @nikomatsakis small nit: the Poll implementations are the interesting ones (not Pin).

    Taylor Cramer at 2020-12-08 23:12:26

  344. Is there anything remaining which suggests this should not be stabilized as is? It seems like inference fallbacks or more general type ascription are both solutions which will improve the ergonomics at a later date, and will not be hindered by stabilizing try blocks as is.

    Noelle Levy at 2021-01-03 19:36:49

  345. but if that's so this seems like pertinent new information that could justify changing the definition of the Try trait.

    Agreed, boats! I've posted an RFC with a potential new Try design: https://github.com/rust-lang/rfcs/pull/3058

    For scoping reasons it doesn't try to specify exactly how try should work, leaving that to a future RFC, but it discusses the problem and some variations in the future possibilities section: https://github.com/scottmcm/rfcs/blob/do-or-do-not/text/0000-try-trait-v2.md#possibilities-for-try (I don't know if it's sufficiently clear about the inference trouble with the "normal" ? desugar. Let me know on that thread or zulip or wherever if there's something you'd like me to expand.)

    scottmcm at 2021-01-10 21:47:04

  346. Agreed, boats! I've posted an RFC with a potential new Try design: rust-lang/rfcs#3058

    I can't figure out - does this block stabilization of "try" as is? In other words, if it were stabilized as is, would that hinder a later move to the design in rust-lang/rfcs#3058?

    Paul Crowley at 2021-01-11 04:38:02

  347. @ciphergoth try { E } as currently implemented on nightly is the "always requires context" version. So the question is whether we want that syntax to be that version. If we do, then we could stabilize it without waiting for 3058.

    The problem is that it's not obvious to me that that syntax should be the one that uses context. Perhaps it would make sense for try { E } to be the one that doesn't use context (and thus doesn't do error-conversion), and for try 🤷 T { E } to be the one that produces a T (and uses that as its context). If that's what we want, then we shouldn't stabilize try { E } right now.

    Said otherwise, see comments such as https://github.com/rust-lang/rust/issues/70941#issuecomment-612167041 for annotation concerns. Someone needs to figure those out before this is ready to be stabilized -- and the resolution there will decide which parts (if any) could be stabilized without 3058.

    scottmcm at 2021-01-11 06:00:11

  348. I don't know if this is a helpful example or just noise at this point, but I recently came across a case where the try block couldn't infer the type despite an explicit turbofish, and where the same code converted to an IIFE (which is the method I use on stable to get something akin to "try block behavior") works just fine. Top one gives compiler error, bottom one works:

    fn using_try(file: File) {
        BufReader::new(file)
            .lines()
            .find_map(|line| try { Ok::<_, std::io::Error>(parse_line(line?)) }.transpose());
    }
    
    fn using_iife(file: File) {
        BufReader::new(file)
            .lines()
            .find_map(|line| (|| Ok::<_, std::io::Error>(parse_line(line?)))().transpose());
    }
    
    error[E0282]: type annotations needed
      --> src/lib.rs:10:26
       |
    10 |         .find_map(|line| try { Ok::<_, std::io::Error>(parse_line(line?)) }.transpose());
       |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type
       |
       = note: type must be known at this point
    

    https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=9b715a78fa6ea9017a1c2426789ae87d

    I really don't know what more type annotation it wants, what I'm returning is unequivocally Result<(), std::io::Error>.

    Alexander Krivács Schrøder at 2021-11-08 13:37:24

  349. Reminder that try{} includes success-wrapping (#70941), so you probably don't want the Ok in using_try.

    One way that works is

    fn using_try(file: File) {
        BufReader::new(file)
            .lines()
            .find_map(|line| Result::<_, std::io::Error>::transpose(try { parse_line(line?) }));
    }
    

    Because inherent method resolution requires that the type is already decided -- when there's a non-trait method call involved, it blocks inference.

    Something like https://rust-lang.github.io/rfcs/3058-try-trait-v2.html#possibilities-for-try would help, since try would no longer need contextual information, and would probably make try { parse_line(line?) }.transpose() start to work without anything else.

    That said, part of the problem is that the method isn't using the result of find_map. For example, this works:

    fn using_try(file: File) -> Option<Result<(), std::io::Error>> {
        BufReader::new(file)
            .lines()
            .find_map(|line| Result::transpose(try { parse_line(line?) }))
    }
    

    (And of course, so does .find_map(|line| line.map(parse_line).transpose()), but that's not important for the try block thread.)

    scottmcm at 2021-11-09 01:12:03

  350. Regarding the issues with type inference, could something like this be done?

    use anyhow::Result;
    
    match try::<Result<()>> {
        some_result_fn()?
    } {
      Ok(()) => (),
      Err(error) => panic!("Error occurred: {:?}", error),
    }
    

    certainly beats the current way:

    use anyhow::Result;
    
    let result: Result<()> = try {
        some_result_fn()?
    };
    
    match result {
      Ok(()) => (),
      Err(error) => panic!("Error occurred: {:?}", error),
    }
    

    If proposing this would require an RFC, just let me know, I haven't contributed to the language that much.

    Julia at 2021-11-25 14:59:23

  351. Wasn't there a proposal for explicit type hints that ought to solve this, that was initially designed for .into() type annotations? IIRC it would allow something like try {…} as Result<()>

    piegames at 2021-11-25 21:32:59

  352. Wasn't there a proposal for explicit type hints that ought to solve this, that was initially designed for .into() type annotations? IIRC it would allow something like try {…} as Result<()>

    I think you may mean type ascription, RFC803, #23416

    Ian Jackson at 2021-11-25 23:20:22

  353. Update: With the new (unstable) ops::Residual trait (#91285) added in #91286, there's a way forward for a version of try{} that would work like array::try_map and Iterator::try_find and friends now do (#85115). I hope to write an RFC to that effect in the coming weeks.

    scottmcm at 2021-12-08 19:09:23

  354. I just got to this issue when I am trying to use ? with try inside a closure, suggested here: https://stackoverflow.com/questions/62687455/alternatives-for-using-the-question-mark-operator-inside-a-map-function-closure , Hope this feature could be stable soon.

    老董 at 2022-01-16 08:21:05

  355. I don't see why this has to be specific to things that return Result/Option/etc. It would be ideal if we could just introduce blocks that acted like ({ ... }) in C or (|| { ... })() in Rust. These would work for any value, not just things that work with ?.

    Elliot at 2022-05-27 17:13:55

  356. I don't see why this has to be specific to things that return Result/Option/etc. It would be ideal if we could just introduce blocks that acted like ({ ... }) in C or (|| { ... })() in Rust. These would work for any value, not just things that work with ?.

    ({ }) blocks in C are the same as { } in rust, they don't create a new function that will be exited when return is used. As I understand it, try { } is functionally equivalent to fn () -> Result<T, E>

    Julia at 2022-05-27 17:16:58

  357. Ah good point. I recall expecting that using ? would exit the { ... } block. Since that's not the current behavior, it would be a breaking to change it. I suppose this is a good move in that case!

    Elliot at 2022-05-27 17:22:34

  358. Since this issue is still open, I figured I'd ask here first before opening a new C-feature-request issue:

    Are there any issues tracking the use of ? in rustdoc examples? IMHO it would allow the documentation to line up better with idiomatic crate usage and rust best practices, instead of being littered with .unwrap() everywhere. More importantly, it would allow docs to clearly distinguish where .expect()/.unwrap() can/should be used (e.g. to "handle" errors caused by a function being called incorrectly) vs cases where the user is expected to employ some form of proper error handling.

    As for how it would behave: it's already allowed to use ? in tests, I imagine the rustdoc example tests would behave similarly.

    Mahmoud Al-Qudsi at 2022-08-02 18:36:25

  359. @mqudsi This is a bit off-topic for this issue. But you can already do this. Use # in your doc comments to hide the set-up. for example

    Peter Hall at 2022-08-02 19:18:46

  360. Rustdoc even has support for detecting a final turbofished Ok value, see the final example under https://doc.rust-lang.org/nightly/rustdoc/write-documentation/documentation-tests.html#using--in-doc-tests.

    Nemo157 at 2022-08-02 21:05:41

  361. I'm curious about the stabilization state of try blocks. Is insufficient type inference the only thing blocking it? And if so, is solving it a backwards-compatibility hazard? I personally think try blocks are already very useful, even if it needs explicit type annotations.

    let result: anyhow::Result<()> = try {
    ...
    

    is not the most unergonomic thing in the world.

    I propose we should attempt stabilizing it, if type inference is not a blocker.

    For functions, the explicit return type of the function drives inference for ?. So I don't think having to annotate a try block with an explicit type would be a big surprise for users.

    crumblingstatue at 2022-09-12 16:10:24

  362. With the announcement of the labeled blocks today, I realized that try {} blocks don’t have a good story for labeled exits. With try { EXPR_A?; try { EXPR_B? } } there isn’t really a good way to throw the error out into the outer try {} block.

    Unfortunately, I also don’t really see a good way to make it work in the future. We can label the try blocks all we want, but then how do we annotate the ?-operator as to which label it should propagate the error to? 'label? seems super odd, to be fair, but it is the best I have so I’ll continue using that syntax throughout the rest of this comment.

    If we do figure out a nice way to attach a label to ?, the value of the try {} construct seems significantly diminished. A lot of what it does can be achieved with plain labeled blocks. Not to mention in such a scheme there is no longer a weird contextual behaviour switch either, which is a huge sticking point to me personally:

    let result: anyhow::Result<()> = 'try: {
        'try_harder: {
            EXPR?;             // returns errors from the function, works the same way `?` always does;
            EXPR 'try?;        // no current proposal handles this;
            EXPR 'try_harder?; // equivalent to `try { EXPR? }` in the current proposal.
            // can still use `break 'try` or `break 'try_harder` too.
            Ok(())
        }
    };
    

    The question of type annotations remain in either case, and while try {} does afford a broader design space in this area (e.g. we could put the error type between try and {), I feel like taking an approach like that would only serve to make try {} to feel like a special case construct, and not mesh with the rest of the language all that well.

    Simonas Kazlauskas at 2022-11-03 21:38:11

  363. On the other hand, unlabeled break targets the closest containing loop (or for or while), not the function. We can't write return 'label nor can we break from the function.

    We could go either way in the pursuit of consistency. We could allow return 'label and prefer that the unlabeled form exits the function, or we could allow break-from-fn and prefer that the unlabeled from exits the closest cover.

    Crystal Durham at 2022-11-03 23:36:22

  364. Do we really need labeled try exits? It seems like the ? operator should only ever break out of a single scope at a time. If you want to propagate things farther you can use a construct like

    let outer = try {
        let a = foo()?;
        let b = try {
            let c = bar()?;
            // ...
        }?; // re-propagate the error from `bar()`, if appropriate
    };
    

    Peter Goodspeed-Niklaus at 2022-11-04 11:01:22

  365. This construct would only really work if the error types are the same, or at least consistently conversible, across the stack of try {}-blocks. If they aren’t, such as would be the case with say outer: Result<_, Box<dyn std::error::Error>>, b: Result<_, std::io::Error> and bar: fn() -> Result<_, std::fmt::Error>, then propagating like this won’t always be straightforward.

    It is true, though, that it is always possible to adjust the code so that values end up where they need to be. Neither try {} nor labeled control flow constructs actually make it possible to do anything new that would otherwise be impossible. Any use of these features can always be desugared to Rust code utilizing simpler constructs. Sometimes a replacement might require sacrificing some readability (code duplication), performance (flag variables) or some other desirable property, but the options are there. In this case the most applicable rewrite would be something like:

    let c = match std::ops::Try::branch(EXPR) {
        ControlFlow::Continue(c) => c,
        ControlFlow::Break(b) => break 'to_anywhere <_>::from_residual(b);
    };
    

    Such desugaring is fortunately isn’t sacrificing any performance. In fact manually desugaring ? might allow simplifying the code further to improve the perf, both at runtime and especially can help compilation times. This sort of snippet is still quite a mouthful compared to some combination of ? and 'label though.


    We could go either way in the pursuit of consistency. We could allow return 'label and prefer that the unlabeled form exits the function, or we could allow break-from-fn and prefer that the unlabeled from exits the closest cover.

    This could be quite nice, if only for macro purposes. With fn being a reserved keyword, return could definitely be “just” a desugaring of break 'fn, just like return is equivalent to (br $frame_depth) in WASM. At that point having return optionally accept a label probably wouldn’t be too outrageous either.

    Simonas Kazlauskas at 2022-11-04 19:36:10

  366. The language team has already previously decided that try blocks do "Ok-wrapping", with the primary motivating example being try { x? } should noop through Try (modulo type inference). (I do not want to relitigate this. I personally agree.)

    <details><summary>desugar</summary>
    match Try::branch(x) {
        Continue(_0) => Try::from_output(_0),
        Break(_0) => Try::from_residual(_0),
    }
    

    </details>

    But there's an interesting question to ask w.r.t. labeled breaks:

    'label: try {
        break 'label expr();
    }
    

    Is the labeled break an "inner" or "outer" break?

    // inner
    try { 'label: {
    
    // outer
    'label: { try {
    

    Would it be different if it were spelled return 'label? I honestly don't know.

    Crystal Durham at 2022-11-04 20:17:08

  367. I think an "outer" break is much more useful here, as it will allow propagation of an error value from within nested blocks.

    Benjamin Saunders at 2022-11-05 01:03:30

  368. Note that yeet is the experimental way of doing an inner break with the error type, and it makes sense that it should be able to be labeled. Assuming some form of yeet is accepted, then it does make some amount of sense to have break be an inner break, so we have the symmetry of

    'label: try {
        if rand() {
            break 'label good();
        } else {
            do yeet 'label bad();
        }
    }
    

    However, the gut reaction I have is that break 'label expr should make the labeled block/expression evaluate to expr (thus an outer break). But the same way the tail expression is wrapped, the same gut says that return 'label expr should get ok-wrapped.

    break being outer also matches how it works with loop, in that break is different from a "tail return" expression in the loop.

    Very interestingly, that suggests a difference between break 'label and return 'label. If we ever add fn-level try, it would apply the same there, in that break 'fn wouldn't get ok-wrapped... but my gut is less sure about that one, despite them being logically very similar.

    And as a very "fun" alternative, just allow 'outer: try 'inner: { and specify whether you want inner or outer break based on which label you target 🙃

    Crystal Durham at 2022-11-05 02:19:18

  369. break from labeled blocks doesn't add any new issues here: if you know how the semantics of try interacts with break from labeled loops, then the semantics for labeled blocks follow.

    Paul Crowley at 2022-11-05 15:05:26

  370. In a strict sense this is true, as you can't label a try block currently. But it's a logical extension to labeled block break to ask why it can't be done on try blocks (and the answer is likely exactly this ambiguity between inner and outer break).

    Crystal Durham at 2022-11-05 18:20:56

  371. I am wondering: would there also be support of a finally block?

    It would be super handy for restoring states, like this:

    // override some states
    let orig_state1 = std::mem::replace(&mut state1, foo);
    let orig_state2 = std::mem::replace(&mut state2, bar);
    let orig_state3 = std::mem::replace(&mut state3, baz());
    
    // perform the fallible action
    try {
        do_dangerous_work(&mut state1, &mut state2, &mut state3)?
    } finally {
        // restore original states
        state1 = orig_state1;
        state2 = orig_state2;
        state3 = orig_state3;
    }?;
    

    Stephen Chung at 2022-11-06 02:20:25

  372. @schungx wouldn't that be possible by using a plain old if expression after the try?

    let orig_state1 = std::mem::replace(&mut state1, foo);
    let orig_state2 = std::mem::replace(&mut state2, bar);
    let orig_state3 = std::mem::replace(&mut state3, baz());
    
    // perform the fallible action
    let result = try {
        do_dangerous_work(&mut state1, &mut state2, &mut state3)?
    };
    if let Err(..) = result {
        // restore original states
        state1 = orig_state1;
        state2 = orig_state2;
        state3 = orig_state3;
    }?;
    

    Arguably this is a little more verbose but I think it reads more clearly than finally, which you have to be familiar with to understand what it does; the if let version just reads as "if the operation failed, revert all modified state."

    riki at 2022-11-06 10:13:58

  373. Well, actually the finally block runs regardless of whether it is an error or not. So it is as simple as:

    let orig_state1 = std::mem::replace(&mut state1, foo);
    let orig_state2 = std::mem::replace(&mut state2, bar);
    let orig_state3 = std::mem::replace(&mut state3, baz());
    
    // perform the fallible action
    let result = do_dangerous_work(&mut state1, &mut state2, &mut state3);
    
    // restore original states
    state1 = orig_state1;
    state2 = orig_state2;
    state3 = orig_state3;
    
    result
    

    It is not difficult at all, but those results do get tedious once you have more than one of them:

    let orig_state1 = std::mem::replace(&mut state1, foo);
    let orig_state2 = std::mem::replace(&mut state2, bar);
    let orig_state3 = std::mem::replace(&mut state3, baz());
    
    // perform the fallible action
    let result =
        do_dangerous_work(&mut state1, &mut state2, &mut state3)
        .and_then(|x| do_some_other_work(x))
        .and_then(|y| do_yet_more_work_that_is_fallible(y));
    
    if result.is_ok() {
        // ... a large block of code
    }
    
    // restore original states
    state1 = orig_state1;
    state2 = orig_state2;
    state3 = orig_state3;
    
    result
    

    With native support this can become:

    // override some states
    let orig_state1 = std::mem::replace(&mut state1, foo);
    let orig_state2 = std::mem::replace(&mut state2, bar);
    let orig_state3 = std::mem::replace(&mut state3, baz());
    
    // perform the fallible action
    try {
        let x = do_dangerous_work(&mut state1, &mut state2, &mut state3)?;
        let y = do_some_other_work(x)?;
        do_yet_more_work_that_is_fallible(y)?;
        // ... a large block of code
    } finally {
        // restore original states
        state1 = orig_state1;
        state2 = orig_state2;
        state3 = orig_state3;
    }?;
    

    Stephen Chung at 2022-11-06 13:23:58

  374. What about:

    // override some states
    let orig_state1 = std::mem::replace(&mut state1, foo);
    let orig_state2 = std::mem::replace(&mut state2, bar);
    let orig_state3 = std::mem::replace(&mut state3, baz());
    
    // perform the fallible action
    let res = try {
        let x = do_dangerous_work(&mut state1, &mut state2, &mut state3)?;
        let y = do_some_other_work(x)?;
        do_yet_more_work_that_is_fallible(y)?;
        // ... a large block of code
    };
    
    // restore original states
    state1 = orig_state1;
    state2 = orig_state2;
    state3 = orig_state3;
    
    res
    

    Erik Bünnig at 2022-11-06 13:52:11

  375. That would work... at the expense of an additional variable.

    But it removes the need to constantly having to remember not to use ? anywhere otherwise it jumps out prematurely without the chance to restore the state.

    Stephen Chung at 2022-11-06 14:23:22

  376. Well, you'd notice as soon as you try to use res: Try, if you put the ? behind the block you'd then have res: Try::Output, so the type checking would probably solve the issue of accidentally using ? where you didn't mean to.

    I think the beauty of Try and try is that we don't need catch or finally keywords to get their behavior, it follows naturally from the control flow of ?.

    Erik Bünnig at 2022-11-06 14:27:11

  377. IMHO Rust should not support a finally block, or at least it should be a separate RFC. Python has a finally and with blocks because the garbage collector cannot be relied upon to call destructors at a predictable time. Object destruction in Rust's memory model is predictable, so we can use the RAII principle to create a guard object whose Drop method will restore the original state. This is more foolproof than relying on the caller of an API to remember to write a finally block. It is also more ergonomic as it avoids the "but those results do get tedious once you have more than one of them" problem @schungx identified.

    Benjamin Kay at 2022-11-06 15:21:53

  378. What are the common idioms for combining try with other language error handling features?

    • match try {} {} to directly handle the result
      • This may look a bit weird over multiple lines
      • This can be avoided with a temporary variable, but not sure if that's better
    • try {}.expect("error") to be able to not-handle errors while also not having to unwrap() everywhere
    • try {}.context("foo")? the equivalent of the above but with anyhow or eyre
    • if let Err(e) = try {} {} kind of discussed today, also similar to match try

    I am mostly worried about weird formatting with longer blocks, having to do extra parentheses and having two code blocks next to each other ({}{}).

    piegames at 2022-11-06 15:37:32

  379. Given that try very often (nearly always?) needs type annotations I would say assigning the result to a local variable and then continuing with whatever you do is the most readable.

    There is no reason in rust to avoid some additional variable assignments (in difference to e.g. C++).

    In case of context due to needing type annotations you probably would use .map_err(|err: Annotation| err.context(....))? instead.


    try {}.expect("error") [...] to be able to not-handle errors [...]

    I prefer using a Panic error type for this which can be converted to from any error and which on conversion panics. Through I mainly use it for testing.

    Philipp Korber at 2022-11-06 15:50:59

  380. I am mostly worried about weird formatting with longer blocks, having to do extra parentheses and having two code blocks next to each other ({}{}).

    match try {
       a
       lot
       of
       code
       in
       here
    } {
        Ok(value) => {
        
        },
        Err(error) => {
            
        }
    }
    

    is reasonable readable I think.

    map_err also looks surprising okay I think (it looks like a catch in many languages):

    let value = match try {
       a
       lot
       of
       code
       in
       here
    }.map_err(|error: Annotation| {
        convert_the_error
    })?;
    

    Philipp Korber at 2022-11-06 15:56:27

  381. Postfix match could also come into play here, try {...}.match {...}, but it's not clear if that has a future.

    Josh Stone at 2022-11-06 16:03:38

  382. so we can use the RAII principle to create a guard object whose Drop method

    RAII with drop guard holding a closure won't work in this case because it locks the state variables until the drop and you'll nerd those variables in your fallible code, otherwise there is no point in setting/restore.

    Stephen Chung at 2022-11-07 01:01:45

  383. RAII with drop guard holding a closure won't work

    Sure, but you don't need that. Something like

    struct RestoreOnDrop<'a, T> {
        value: &'a mut T,
        original_value: T,
    }
    
    impl<'a, T> RestoreOnDrop<'a, T> {
        /// Temporarily substitute a new value into `value`. The associated `Drop` impl restores it.
        fn new(value: &mut T, set_to: T) -> Self {
            let orgiinal_value = std::mem::replace(value, set_to);
            Self { value, original_value }
        }
    }
                
    impl<'a, T> Drop for RestoreOnDrop<'a, T> {
      // put it back
    }
    
    impl<'a, T> Deref for RestoreOnDrop<'a, T> { ... }
    impl<'a, T> DerefMut for RestoreOnDrop<'a, T> { ... }
    

    Now you don't need any finally clause. Instead, you have

    // override some states
    let mut state1 = RestoreOnDrop::new(&mut state1, foo);
    let mut state2 = RestoreOnDrop::new(&mut state2, bar);
    let mut state3 = RestoreOnDrop::new(&mut state3, baz());
    
    // perform the fallible action
    // `RestoreOnDrop` restores original states automatically on drop
    try {
        let x = do_dangerous_work(&mut state1, &mut state2, &mut state3)?;
        let y = do_some_other_work(x)?;
        do_yet_more_work_that_is_fallible(y)?;
        // ... a large block of code
    }
    

    [edit] we can return the happy-path value implicitly.

    Point is, we don't really need finally for try to be a good, complete feature.

    Peter Goodspeed-Niklaus at 2022-11-07 01:12:02

  384. With a bit of modifications the idea from @coriolinus seems to work fine for my purposes, without even resorting to try.

    For future comers, here's what I have:

    //! Facility to run state restoration logic at the end of scope.
    
    use std::ops::{Deref, DerefMut};
    
    /// Run custom restoration logic upon the end of scope.
    #[must_use]
    pub struct RestoreOnDrop<'a, T, R: FnOnce(&mut T)> {
        value: &'a mut T,
        restore: Option<R>,
    }
    
    impl<'a, T, R: FnOnce(&mut T)> RestoreOnDrop<'a, T, R> {
        /// Create a new [`RestoreOnDrop`] that runs restoration logic at the end of scope only when
        /// `need_restore` is `true`.
        #[inline(always)]
        pub fn new_if(need_restore: bool, value: &'a mut T, restore: R) -> Self {
            Self {
                value,
                restore: if need_restore { Some(restore) } else { None },
            }
        }
        /// Create a new [`RestoreOnDrop`] that runs restoration logic at the end of scope.
        #[inline(always)]
        pub fn new(value: &'a mut T, restore: R) -> Self {
            Self {
                value,
                restore: Some(restore),
            }
        }
    }
    
    impl<'a, T, R: FnOnce(&mut T)> Drop for RestoreOnDrop<'a, T, R> {
        #[inline(always)]
        fn drop(&mut self) {
            if let Some(restore) = self.restore.take() {
                restore(self.value);
            }
        }
    }
    
    impl<'a, T, R: FnOnce(&mut T)> Deref for RestoreOnDrop<'a, T, R> {
        type Target = T;
    
        #[inline(always)]
        fn deref(&self) -> &Self::Target {
            self.value
        }
    }
    
    impl<'a, T, R: FnOnce(&mut T)> DerefMut for RestoreOnDrop<'a, T, R> {
        #[inline(always)]
        fn deref_mut(&mut self) -> &mut Self::Target {
            self.value
        }
    }
    

    To use this:

    {
        let some_state_object: &mut SomeStateObject = ...;
    
        let orig_state = some_state_object.get_state();
    
        let some_state_object = &mut *RestoreOnDrop::new(some_state_object, move |state| state.restore_state(orig_state));
    
        // use `some_state_object` normally, changing it etc.
    }
    // ... exiting the scope, the closure is run automatically, restoring the original state
    

    Stephen Chung at 2022-11-08 03:28:38

  385. After the discussion about ExprWith(out)Block in the lang team meeting regarding const{}, I came to double-check what we do for try{}, and it looks like it's an ExprWithBlock, as it parses fine without a terminator comma in a match art.

    Oh, and here's the code about it: https://github.com/rust-lang/rust/blob/70fe5f08fffd16dc20506f7d140e47b074f77964/compiler/rustc_ast/src/util/classify.rs#L7-L25

    FYI @joshtriplett

    scottmcm at 2022-11-22 21:23:53

  386. TLDR: I think the guards don't solve finally, I would use this lots without finally anyway. Long version:

    The thing about the RAII guards that are being proposed is that they need an explicit commit step if you want to keep the changed state (e.g. std::mem::forget). I agree that try without finally would be useful and in fact we have a lot of places in our codebase where we literally make up for it by having a second function just to get that wrapping behavior. But the RAII guards don't solve finally in my opinion because the mistake moves from "I didn't write a finally block" to "I forgot to explicitly commit all the guards".

    Put more formally, we have:

    1. Finally as a cleanup block.
    2. Finally as a rollback of state to consistency.

    1 isn't needed because Drop. But replacing 2 with guards moves "I forgot my finally block so we crash on error" to "I forgot my finally block so we have hard to spot things that never commit in this transaction-ish abstraction". You end up having to enumerate the state twice (once to set the guards, once to commit them), which introduces two spots for human error as opposed to one, and in a code review context is going to require the reviewer to notice that a commit is missing.

    Now all that said, I can't think of a single time in Rust that I have actually needed finally save perhaps for logging, and I find that in larger applications I and my colleagues end up having to treat errors as this failed horribly and figuring out what consistent state we're in is probably impossible so die hard and loudly. I'm speaking from a web microservices perspective there but I do have more mathematical side projects in C++ (and recently Rust) and haven't needed finally there much either. In fact the only examples I can think of are some custom ringbuffers where the finally block would set some atomic counters to specifically record that the slot is corrupt, and in fact I can't even remember if I kept that code.

    Austin Hicks at 2022-12-05 03:15:15

  387. Ok so I realized after I posted my last comment that finally blocks always run anyway, which just goes to show how often I have used them for something. I think the general point of it still sort of stands, but eh. Sorry. Consequence of doing Rust full-time is we forget how languages with exceptions work apparently...

    Austin Hicks at 2022-12-05 04:14:25

  388. Ok so I realized after I posted my last comment that finally blocks always run anyway,

    That's correct. Thats why they are often used to do clean-up as well to revert back to a state "up the original level".

    Normally such level-based states can be kept in a stack structure (or the call stack). Using finally makes it simple to do it ad hoc.

    Whether it is a good thing is debatable though.

    Stephen Chung at 2022-12-05 05:19:46

  389. A minor note about syntax, but now that we have let-else blocks, try-blocks seems like a natural fit for them. I think the ideal syntax would look like:

    let Some(value) = try {
        do_something()?;
        do_something_else()?;
        do_yet_more_things()?
    } else {
        Some(other_value)
    };
    

    This doesn't compile right now because the compiler doesn't allow a right curly-brace before the else in a let-else statement. I think this is related to nesting if-let-else statements in let-else statements, but I wonder if it's possible to accommodate try-blocks in this position without wrapping them in brackets. It's a minor syntax thing, but losing the brackets reads far more clearly to me.

    Jeremy Haak at 2022-12-05 23:06:16

  390. Just for completeness, note that if this is using let-else, it would need to be a fallible binding, and the else case would need to diverge, e.g.

    let Some(value) = (try {
        do_something()?;
        do_something_else()?;
        do_yet_more_things()?
    }) else {
        panic!()
    }
    

    If the goal is to provide a default value in the case that the try block failed and continue executing the current function, that could be (after the otherwise needed type inference adjustments)

    let value = try {
        do_something()?;
        do_something_else()?;
        do_yet_more_things()?
    }.unwrap_or_else(|err| {
        some_other_value
    });
    

    using the normal Result/Option functionality.

    If you try to use the original example you'll (after wrapping the try block in parentheses) end up with a warning that the pattern is infallible and the else branch is never taken, along with an error that the else branch doesn't unfortunately diverge, along with whatever errors resulting from value being Option<T> instead of T.

    The large semantic difference between let $pat = (try {…}) else {…} and let $pat = (try {…} else {…}) are a very good reason not to provide a let-else construct.

    Crystal Durham at 2022-12-05 23:23:49

  391. I think try...else boils down to whether or not try is still supposed to be able to work beyond Result. I'm not sure if that's the case because there is a lot of backlog I haven't read, but if people have now decided it's Result only, then I think it might be worth continuing that idea.

    Austin Hicks at 2022-12-06 01:43:51

  392. try is still intended to handle all types that ? in functions does. The discussed potential restriction is that the "carrier" type wouldn't be able to change (i.e. if you ? Result, you can only ? Result in that block, and the unadorned try would always be Result) and potentially that the "residual" type wouldn't be able to vary (i.e. if you ? io::Result, you can only ? io::Result in that block, the unadorned try would always be io::Result, not e.g. anyhow::Result).

    Crystal Durham at 2022-12-06 03:41:22

  393. Thanks, that's useful.

    I'd be against residuals being fixed for what that's worth. I can't think of many cases in which we have a block of code that's large enough to justify try blocks if the residuals have to be the same. We adopted anyhow heavily because we found that large enough codebases for the kind of software we write result in reinventing anyhow with e.g. thiserror, so we just decided to not do that, but it does mean that we have places that heavily mix anyhow with other error types.

    I'm not sure I'm adding a lot to the discussion, but if people want details on our error handling strategies I'm happy to elaborate. We use Rust heavily in prod and most of our Rust can go months without issue.

    Austin Hicks at 2022-12-06 16:12:24

  394. Address issues with type inference (try { expr? }? currently requires an explicit type annotation somewhere).

    Do we have a solution for this yet? if not I propose the following syntax:

    try -> Result<T, E> {
        expr1?
        expr2?
    }?;
    

    This is analogous to how I've been using ad-hoc try blocks with closures.

    (|| -> Result<T, E> {
        expr1?
        expr2?
    })()?;
    

    Senne Hofman at 2022-12-08 13:19:32

  395. Another potential solution could be to just modify how type inference works.

    Unless the try block has it's type constrained like so (or any other method):

    fn foo() -> Result<u32, Error> {
        let res: Result<u8, OtherError> = try {
            expr1()?
            Ok(5_u8)
        }
        Ok(res? as u32)
    }
    

    Then the error returned by the try block should be the same as the error from the function's result:

    fn foo() -> Result<u32, Error> {
        // Has type of Result<u8, Error>
        let res = try {
            expr1()?
            Ok(5_u8)
        }
       Ok( res? as u32)
    }
    

    This would allow you to do stuff like try { expr? }? without a problem. Unfortunately this adds a bit more "magic" to the try block but considering type inference is already pretty "magic" I don't believe this to be a problem.

    miam-miam at 2023-02-01 10:15:48

  396. Is the labeled break an "inner" or "outer" break?

    Questions like this are why label-break-value only supports ordinary blocks, not else blocks, unsafe blocks, async blocks, etc.

    So if you want to label-break-value from a try block, it's 'label: { try { ... } } or try { 'label: { ... } } depending which you want.

    Do we have a solution for this yet?

    I'm working on a plan to have unannotated try { be homogeneous, using the same Residual technique that Iterator::try_find uses in nightly.

    That way simple things like try { a?.b?.c?.d? } works without needing extra stuff, so long as the ?s all have the same residual type.

    (Then figuring out heterogeneous try can wait for later, since the existing workarounds people are already using handle that case.)

    scottmcm at 2023-02-01 18:57:01

  397. I have one question: why not have ? work in normal blocks? What should try be necessary?

    giluis at 2023-02-27 16:18:12

  398. I have one question: why not have ? work in normal blocks? What should try be necessary?

    This is a fair question, @giluis. Suppose we have this very contrived example:

    impl From<OhNo> for UhOh {
        // ...
    }
    
    fn do_something() -> Result<(), UhOh> {
        let result: Result<(), OhNo> = try {
            do_the_thing()?
        };
        match result {
            Err(_) => Err(UhOh::reason("We had an Oh-No!")),
            Ok(()) => Ok(()),
        }
    }
    

    Since From<OhNo> is implemented for the error type UhOh, at least in theory, do_the_thing()? could return immediately without going through the subsequent match statement. The try tells the compiler, "Don't return from the function when you see a ?, just unwind to the last try block."

    Benjamin Kay at 2023-02-27 16:34:28

  399. It is quite simple to use map_err to deal with this situation:

    do_the_thing().map_err(|_| Err(UhOh::...))?;
    

    So try should be used in instances where you don't want to escape the entire function block (but only a portion of it). It is probably not very necessary for remapping errors.

    Stephen Chung at 2023-02-27 16:54:56

  400. It is quite simple to use map_err to deal with this situation

    Yes, but I think the problem is with try {} blocks that have many statements in them, all with a ?. You want to capture any of those errors, and not return from the whole function.

    Chris Smith at 2023-02-27 18:45:50

  401. Not sure if it's a known issue but this fails to compile on latest nightly compiler:

    #![feature(try_blocks)]
    fn foo(cond: bool) -> impl Iterator<Item=i32> {
        let result: Option<i32> = try {
            if cond {
                println!("Oops");
                return None;
            }
            55
        };
        result.into_iter()
    }
    
    error[E0277]: `Option<_>` is not an iterator
     --> src/lib.rs:2:23
      |
    2 | fn foo(cond: bool) -> impl Iterator<Item=i32> {
      |                       ^^^^^^^^^^^^^^^^^^^^^^^ `Option<_>` is not an iterator
      |
      = help: the trait `Iterator` is not implemented for `Option<_>`
    

    This might be an incorrect usage but at least error message might a clearer.

    Psilon at 2023-03-04 17:15:52

  402. error[E0277]: Option<_> is not an iterator

    Doesn't look like an issue to me, the error message is correct: Option does not implement Iterator, only IntoIterator. And you can get the same error without using try_blocks.

    BTW, the compiler goes on with:

    error[[E0308]](https://doc.rust-lang.org/nightly/error_codes/E0308.html): mismatched types
      --> src/lib.rs:10:5
       |
    2  | fn foo(cond: bool) -> impl Iterator<Item=i32> {
       |                       ----------------------- expected because this return type...
    ...
    6  |             return None;
       |                    ---- ...is found to be `Option<_>` here
    ...
    10 |     result.into_iter()
       |     ^^^^^^^^^^^^^^^^^^ expected `Option<_>`, found `IntoIter<i32>`
       |
    

    So, you can easily fix this by adding into_iter() to your return statement: playground

    Or you replace the return statement with None?: playground

    Cryptjar at 2023-03-04 17:56:10

  403. ~~try blocks look and feel like monadic functions.~~

    ~~Can they be passed around? Do they work for any monadic type? How do they affect the type signature of HOFs?~~

    Didn't read the docs closely enough

    Will at 2023-03-25 18:13:05

  404. Some my thought about try-catch

    I think maybe it would be better to use .try syntax instead of ?, it will simplify lots of things. It will be one syntax for all try actions.

    catch on other hand could be something like this:

    do {
        do_something().try;
    } catch {
    
    }
    

    Denis at 2023-03-27 11:42:19

  405. I'm not sure what a .try suffix solves, but it allows for the following mistake:

    do {
        thing1().try;
        thing2()?; // oops, early return.
    } catch ...
    

    Austin Hicks at 2023-03-27 15:15:14

  406. @redradist That seems like much more confusing syntax than ?:

    1. There's already the ? operator; introducing a new operator doing "the same thing" would 100% be confusing, especially if they're subtly different in their behavior.
    2. As far as I'm aware, the only existing case of a "field-like" operator is .await. Unlike .await, however, .try could alter control flow by breaking out of a try block (or, in case it behaved exactly like ?, early returning), which would IMHO be very confusing behavior for something that looks like a normal field access.

    Freyja at 2023-03-27 15:30:45

  407. There is kind of indirect precedent for a .try via .await. .await is actually a "cancellation point", it's just that few people knowingly use it that way. This is what happens with e.g. tokio::time::timeout, though.

    I like the .await syntax but debate ranges far and wide with respect to whether that was a good idea last I heard. Given that whatever .try did would have to function harmoniously with ?, I don't see the value unless there's something it can do that I'm missing, and if there is then there would almost inevitably (and, perhaps, rightly) be a bikeshed around whether it's try foo or foo.try.

    Austin Hicks at 2023-03-27 19:59:36

  408. @frxstrem

    @redradist That seems like much more confusing syntax than ?:

    1. There's already the `?` operator; introducing a new operator doing "the same thing" would 100% be confusing, especially if they're subtly different in their behavior.
    
    2. As far as I'm aware, the only existing case of a "field-like" operator is `.await`. Unlike `.await`, however, `.try` could alter control flow by breaking out of a `try` block (or, in case it behaved exactly like `?`, early returning), which would IMHO be very confusing behavior for something that looks like a normal field access.
    

    My idea was to replace ? with .try syntax for all try cases:

    1. Returning Result<T, E>
    2. Returning Option<T>

    Also it more visible do_something().await? -> do_something().await.try and more better to searching using regex, because .try easier to find that ? which is overloaded depending on context

    Denis at 2023-03-28 06:11:20

  409. Changing existing syntax is out of scope for this tracking issue and best served by a separate discussion. Especially if your proposal is a pure syntactic change, because then it is orthogonal to the feature of try blocks.

    piegames at 2023-03-28 10:52:51

  410. Is there any way of early-returning from a try block? I have the following code:

    let res = try {
        let a = get()?;
        
        if check(a) {
            return None;
        }
        
        Some(perform(a))
    };
    

    Unfortunately the return None attempts to return from the function and not the try block. do yeet None also doesn't work, because the return type is Result<_, _>.

    (I know for this specific case I could use an else clause with the Some(perform(a)), but I'd like to use several early returns where that isn't possible)

    Filipe Rodrigues at 2023-04-03 09:50:15

  411. @Zenithsiz this is possible in general using break and a labeled block:

    let res = 'early: {
        try {
            let a = get()?;
            if check(a) {
                break 'early None;
            }
            Some(perform(a))
        }
    };
    

    In addition, to use do yeet with option, the “error” value is unit, so you’d write do yeet (). Which does appear to work with try blocks, but break type inference in this specific example.

    zopsicle at 2023-04-03 09:53:32

  412. @chloekek Thanks for the workaround, I needed to move the label to the inside of the try block, else I'd need to use break 'early Ok(None).

    Which begs the question, if labels were allowed on try blocks, I would assume you could just use break 'a None, right? As-if the label was on the inside of the try block?

    Filipe Rodrigues at 2023-04-03 10:01:47

  413. (I believe this was discussed earlier in the thread;) labeled break on try blocks isn't allowed in not insignificant because of the ambiguity of whether such a break would be considered "inner" or "outer". Using a separate block is immediately clear.

    Crystal Durham at 2023-04-03 11:01:30

  414. Address issues with type inference (try { expr? }? currently requires an explicit type annotation somewhere).

    Just my two cents:

    try T ? E {
        do_something()?;
        do_something_else()?;
        do_yet_more_things()?
    }?;
    

    The intuition behind this syntax is essentially, "this block will try to get back an ok value of type T, but the ? operator may propagate an error of type E instead".

    Michael Yang at 2023-09-01 11:52:01

  415. I don't like the syntax @my4ng suggested. It gives the impression of being a condition like with if or while statements, or some other statement like with match statements. A type definition appearing there without a colon or similar in front is not in line with existing statements and could be confusing.

    msrd0 at 2023-09-01 13:39:10

  416. Another idea for optional syntax to help with type inference

    try::<T,E> {
       …
    }
    

    Very possible this has already been floated but throwing it out there in case it hasn’t.

    Andrew Burkett at 2023-09-01 13:56:36

  417. try::<T,E> {
       …
    }
    

    that doesn't work with types that have no error type, e.g. Option<T> or other custom types

    Jacob Lifshay at 2023-09-01 15:09:33

  418. @msrd0 yes thanks for pointing that out, you are correct that it should reflect more as a type annotation rather than a condition. Would a turbofish syntax be most appropriate considering type ascription has been de-RFCed?

    Michael Yang at 2023-09-01 15:25:25

  419. The obvious syntax for one type would be try::<T>, but would that be infinite lookahead? I suppose not if it was handled later after the grammar.

    Austin Hicks at 2023-09-01 16:00:53

  420. To clarify a bit, since I was on my phone earlier. My thinking with that syntax is that if try {} had a theoretical function signature it could look something like

    fn try<T, E, O>(block: impl FnMut() -> O) -> O where O: Try<Output=T, Residual=E>
    

    In that case, you would basically be specifying the types that are needed and leaving out ones that aren't either by excluding trailing ones or using _ for non-trailing.

    That said, I have no idea if this is possible or even if it's a good idea.

    Andrew Burkett at 2023-09-01 17:08:05

  421. I don't know if this is possible either but one option here would be to use ! as the nth where n != 1 type. Unfortunately I don't think that's stable enough though? Not sure what the latest status of it is.

    E.g. try::<MyThing, !> for one type.

    But is why we don't just have it be try::<Option<T>> or whatever in here anywhere? What is the value in allowing the user to specify the inner types if specifying the outer type isn't that much more work in the cases wherein this is necessary? E.g. try::<Result<Thing, _>> could be valid syntax, just like everywhere else with types. I don't think this is supposed to be somehow heterogeneous such that you can use ? on both Option and Result in the same block or anything, right?

    Austin Hicks at 2023-09-01 19:41:17

  422. I feels to me like the annotation is annotationg the try result, similar to a function result, rather than some genric parameter,s as with function or type generics.

    try -> Result<MyThing, _ > {
     // ... block content ...
    }
    

    landing at a mix of @drewkett and @ahicks92

    Bennet Bleßmann at 2023-09-02 19:21:04

  423. What's actually missing though? I feel like if it was just syntax that was the problem with type inference, this would be done.

    if I can rewrite some hypothetical syntax to some syntax that exists today and it does the same thing then that indicates that more must be involved, right? Like, I can also pretty much do:

    let try_body = || -> Result<T, E> {
        // stuff!
    }
    try_body();
    

    "pretty much" because the compiler doesn't understand that the closure is always called in my experience, but even so.

    A skim doesn't turn up this context, if there is any to turn up.

    Austin Hicks at 2023-09-02 21:01:13

  424. As fare as I understand try allows one to return from the function, while using an IIFE does not allow one to return from the outer function, so there is a difference.

    Bennet Bleßmann at 2023-09-02 21:10:38

  425. Yes, they aren't exactly equivalent. I could provide an exactly equivalent desugaring, but that's not the point/question I'm aiming at. The closure-based desugaring has other problems based around the compiler not "seeing through" it anyway that make it much less useful than it could otherwise be. What I'm saying is: is syntax for naming the type really the issue, given that if that's all that were left this would probably be stabilized? being able to provide something resembling a desugaring on current stable Rust implies that it's not because otherwise the smart people in charge would probably have finished this off by now.

    Austin Hicks at 2023-09-02 22:19:58

  426. I feels to me like the annotation is annotationg the try result, similar to a function result, rather than some genric parameter,s as with function or type generics.

    try -> Result<MyThing, _ > {
     // ... block content ...
    }
    

    landing at a mix of @drewkett and @ahicks92

    Already proposed by @senneh and I think try: T { ... } has been the proposed syntax for years.

    Matthew McAllister at 2023-09-10 23:58:47

  427. I feels to me like the annotation is annotationg the try result, similar to a function result, rather than some genric parameter,s as with function or type generics.

    try -> Result<MyThing, _ > {
     // ... block content ...
    }
    

    landing at a mix of @drewkett and @ahicks92

    Already proposed by @senneh and I think try: T { ... } has been the proposed syntax for years.

    I wasn't aware of their proposal in https://github.com/rust-lang/rust/issues/31436#issuecomment-1342726776 (it was part of the now 477 hidden items folded by GitHub), and the earliest mention of try : T { ... } I can find in this issue is https://github.com/rust-lang/rust/issues/31436#issuecomment-603028407, though it does not appear to be the source.

    I think I would prefer try -> T { ... } over try: T { ... }, as I find try more similar to closure/function return type ascription than const/static/let type ascription and would be opposed to try::<T> { ... }, as I don't think the generic path syntax fits.

    Bennet Bleßmann at 2023-09-11 10:20:59

  428. I came back to check on this since I very, very much want to have Try blocks in Rust, to the point where I've started writing IIFEs (gasp) as a way around it:

    let poor_persons_try = (|| {
        let arg = do_something(...)?;
        do_something_else(arg)
    })();
    

    This also kind-of draws into question whether the type inference issues should really block the stabilisation of this, since those could be improved post-stabilisation, and I would expect that any poor type inference wouldn't perform worse than the above.

    Clar Fon at 2023-09-26 22:54:35

  429. FYI I also use IIFEs and in my experience it's much more common to need to annotate the error type not the value type. Thus I often write || -> Result<_, Error> {}. I don't see anything wrong with it and having an optional -> Result<T, E> seems like the most elegant solution to the inference problems. It's totally obvious what's going on and sometimes isn't needed.

    Martin Habovštiak at 2023-11-10 10:29:47

  430. In all honesty I find that anywhere a Result is used, I annotate either:

    • The error type
    • The result type itself, e.g. "please use anyhow::Result", which is equivalent to annotating the error type.

    Annotating whatever we call the non-error type (surely this has a name) is a distant third and usually it's with .collect, specifically .collect::<Result<Container<_>, _>>() (I mean _ literally here; the compiler just needs help knowing the container type).

    Austin Hicks at 2023-11-10 16:31:52

  431. I feels to me like the annotation is annotationg the try result, similar to a function result, rather than some genric parameter,s as with function or type generics.

    try -> Result<MyThing, _ > {
     // ... block content ...
    }
    

    landing at a mix of @drewkett and @ahicks92

    I think the syntactic similarity to a function definition would be harmful because it suggests that try is function-like, and so return inside the try block would “return” from the try block (akin to labeled break in any block). But this is not what would actually happen; return in a try block returns from the outer function. try: T {} looks less function-like and more regular-block-like.

    But try: T {} is also problematic because it kind of resembles the syntax of variable assignment, where {} is an ordinary block in which ? will return early from the current function, not just the block. The singular behavior of try blocks — ? returns early as it does in a function, but return returns from the outer function as it does in a block — warrants syntax distant from anything familiar so as to avoid suggesting that try blocks are like anything else in Rust. We don't want to give people the feeling that they can use their intuition when they cannot.

    For this reason I actually think we should revisit the spelling of try altogether. I like try? {} and try?: T {} (literally just add the question mark, don't change any behavior, and I guess special-case it in the parser so that try? is the keyword). No other Rust keyword changes the semantics of the language the way try will, and so I think that instead of trying to make it fit existing syntactic conventions (with, say, try -> T {}) we should be doing the opposite and making it look as idiosyncratic as possible, while at the same time remaining suggestive of its functionality.

    try?: MyResult {
       // ...
    }
    

    Robert at 2024-01-02 22:01:49

  432. I think that try type annotations will probably not be a thing for the same reason that generic type ascription got removed from the compiler: pretty much all solutions end up being just a worse version of let x: T = (expression). So, for a try block, let x: Result<MyThing, _> = try { ... } seems like an okay alternative to pretty much everything proposed here.

    Ultimately, we're talking about a miniscule number of characters saved here, and a bunch of weird inference bugs that could result. Doesn't really seem like a priority over just getting the existing syntax out there.

    Clar Fon at 2024-01-03 06:38:45

  433. @clarfonthey oh, yeah, that works too. An alternative could be added later anyway.

    Martin Habovštiak at 2024-01-03 19:03:29

  434. @clarfonthey I completely agree. Not only does let x: Result<A, B> = try { ... } seem simpler, it also resembles the same thing for futures (let x: impl Future<Output = A> = async { ... }). This will make it consistent across the language and makes the syntax easier for people, especially new-comers, to understand

    Rakshith Ravi at 2024-01-03 19:09:50

  435. Is all that is required here a stabilization PR proposing that the assignment notation for inferring the type is consistent with other block types such as async, and is preferable? Or are there still other blockers?

    Edit: here is basically the same question asked in September 2022: https://github.com/rust-lang/rust/issues/31436#issuecomment-1243966462

    Jeremiah Senkpiel at 2024-01-08 20:18:34

  436. I also believe that bad inference shouldn't be a blocker, we have already merged RPITIT with improving the iffy parts later on, so there is a precedent.

    This is also a very wanted feature by many people in the community and if this was stabilized just by having people using it they will notice the most common problems with type inference and will likely work on it, give solutions, ideas etc.

    Every good work of software starts by scratching a developer's personal itch. – Eric Raymond

    Orion "Ardi" Gonzalez at 2024-01-25 20:37:42

  437. Suggestion: what if the ? in try-blocks by default performs only the early return, without any error conversion? In my code, most commonly I find that I don't really need the conversion functionality, and it just gets in the way of type inference. I.e. the suggestion is, if the return error type of a try-block isn't explicitly annotated, then all error types in ? operations must be the same, and that is the error type returned by the try-block.

    The obvious downside is that it's obviously inconsistent with the behaviour of ? in closures and functions. On the other hand, it removes the type inference ambiguity in the quite common case of the precise error type, and it's also a bit faster for compilation, since we aren't dumping redundant conversions into the optimizer.

    Anton at 2024-02-01 12:20:55

  438. This is consistent with ? in functions (since the return type is always annotated) but inconsistent with its use in closures. I've often had type annotation issues with closures as well for the same reason.

    Andrew Farkas at 2024-02-01 15:34:40

  439. So, that'd render it useless for most code I work on, because ours goes the other way. We almost always rely on the implicit conversion of ?, and when working cross-crate or cross-module you almost never have the same error type. E.g. call aws sdk, do disk i/o, call whatever other thing, and so on--all of those are going to use a different error. Eventually we "gave up" for lack of a better word and adopted anyhow, which solves the microservices connecting lots of things together problem quite nicely. If you already design so that operations are idempotent as it is, then error handling becomes primarily about knowing if something failed and being able to log it with a side of not punishing people for raising errors with lots of details, and less about introspection of the errors, save in specific limited cases. Connect enough libraries and combinatorial explosion kills any hope, plus one must design for server failure/process gets killed/etc anyway, so it ends up just being easier to design for "might stop and explode at any time" and that turns into some sort of dynamic error container pretty quick.

    But once you have, ? really becomes more of a monadic lift to the dynamic container (in our case anyhow) and the conversion becomes essential.

    Maybe it's worth stabilizing something that requires unification, but it wouldn't get me to use the feature. I do see value in the other error handling pattern wherein someone does actually make sure to use the same error type, though it is hard for me to think of many complex cases that would only use one error type, simply because of dependencies all bringing their own. It'd also be surprising behavior anyway: the ? operator would then have slight differences depending on if it's in a try {}. For the extreme case, imagine how "fun" this is for macros.

    Austin Hicks at 2024-02-01 20:42:18

  440. To be clear, @afetisov was suggesting that the conversation still happens when the return type on the try block is annotated.

    Andrew Farkas at 2024-02-01 23:27:34

  441. I feel like that when try { } blocks are nested, the inference from the inner ones should be that of the outer ones.

    That is the inference solver could maybe forward the inference constraints from inner try blocks to outer try blocks (if they are the direct parent in the AST). I think it's fine to error and ask for annotations if that does not lead to a inference solution.

    More concretely, given:

    #![feature(try_blocks)]
    
    fn main() {
        match try { 
            let r: Result::<(), ()> = try {
                Err(())?;
            };
            r?;
        } {
            Err(()) => (),
            Ok(()) => (),
        }
    }
    

    It seems very reasonable that r (even if no variable is made) should attempt to instead see if a solution can be found in the match try { }, and ask for annotations if that does not solve.

    Are there cases where this would obviously not work? And, are there inference problems which are not related to try block nesting?

    Edit: Ok I suppose this is really asking why .into().into() doesn't work even for cases where an intermediary type would be irrelevant or non-existent, which is probably it's own problem.

    Edit 2: It seems the inference problem can be summed up by this code which was linked to from the try-trait-v2 rfc: https://github.com/rust-lang/rust/blob/7cf205610e1310897f43b35713a42459e8b40c64/compiler/rustc_codegen_ssa/src/back/linker.rs#L529-L573

    Jeremiah Senkpiel at 2024-02-02 01:55:26

  442. Isn't the core part of this discussion blocking at least in part on syntax to annotate the blocks, though? I did slightly misread, to be fair--if annotating causes the conversion to happen then yeah that works for me and the codebases I work on. But I think that "pruning" ideas might be the better thing to do, and nothing says that Rust can't just shrug and go "always annotate" and land. There's definitely precedent for that by now, e.g. rpitit.

    Austin Hicks at 2024-02-02 05:50:31

  443. So after reading the small pile of RFCs which are in one way or other related to this feature (but not well linked from the OP) (probably because the OP outdates some of these):

    There doesn't seem to be any comprehensive reference for try { } blocks. The original RFC is very light on specifics regarding catch { }, the try expressions RFC only deals with the naming, and the try trait v2 RFC leaves it to another RFC.

    From the try trait v2 RFC:

    So a future RFC could define a way (syntax, code inspection, heuristics, who knows) to pick which of the desugarings would be best. (As a strawman, one could say that try { ... } uses the "same family" desugaring whereas try as anyhow::Result<_> { ... } uses the contextual desugaring.) This RFC declines to debate those possibilities, however.

    So it seems to me that the best way to move forward here would really be to make a dedicated try blocks RFC where a reference can be made for what the implementation should conform to, and try to make a direction for how to handle the inference problems, as well as documenting which concerns actually need to be figured out before stabilization and which can be left for future possibilities.

    Maybe that's a bit heavy handed, and perhaps people don't think that's necessary but that's probably the direction I would opt to take before wanting to make PRs to adjust the compiler inference for try blocks in specific... if no one directs otherwise an RFC is what I plan to do...

    Edit: It seems I missed the try-blocks Zulip discussion from 2022:

    In the top of that thread, scottmcm says:

    The problem is that I think the vanilla form (of try blocks inference) should do something different, which isn't a backwards-compatible change.

    I've got an RFC started; ...

    For those who don't want to dig into the thread, there was a draft at https://github.com/scottmcm/rfcs/blob/try-again/text/0000-resolving-try-annotations.md#summary

    Jeremiah Senkpiel at 2024-02-02 23:51:05

  444. I think that before another RFC, a useful step forward would be to summarize all the existing RFCs and discussions from both GitHub and Zulip in a single document, so that it is easier to see the status quo, and the unresolved questions and blockers.

    Jakub Beránek at 2024-02-03 07:14:33

  445. I think that before another RFC, a useful step forward would be to summarize all the existing RFCs and discussions from both GitHub and Zulip in a single document, so that it is easier to see the status quo, and the unresolved questions and blockers.

    I don't agree. All of the discussion should happen here and all of the relevant points should be raised here. If anyone has heard of anything they might point to it but I don't think that it makes sense to require a document because nobody wants to go over all threads compiling information.

    It is my understanding that the only question left is about the inference, and based on the general sentiment that's not a blocking issue.

    I propose to stabilize!

    Orion "Ardi" Gonzalez at 2024-03-02 00:58:11

  446. Of course that no one wants to do it, it's a lot of work, but that's what it takes to move these big features forwars :) For a stabilization PR, we'd need to do that anyway, to summarize the current state and precisely specify what are the current unresolved questions, potential blockers and alternatives.

    Jakub Beránek at 2024-03-02 07:44:01

  447. @dev-ardi All of the discussion should happen here

    Actually, no. Discussion should be linked into the tracking issue, but tracking issues are not intended to be where discussion takes place. From the current tracking issue template:

    Tracking issues are used to record the overall progress of implementation. They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions. A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature. Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.


    It was my understanding that try blocks without a "return type annotation" were weakly intended to move to "residual preserving" semantics before stabilization. But still separately from that, I expect stabilization of Try itself is blocked on improving documentation around the Residual type. (Personally, I wish "stem inter-conversion" hadn't been used for Poll and poll? had been ready!(poll) instead. Unfortunately, that's quite unlikely to be changed even over an edition.)

    Crystal Durham at 2024-03-03 22:50:43

  448. I asked @scottmcm if he still wanted to pursue his RFC in the Zulip thread, but with no reply https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/try.20blocks/near/419566066

    I think that before another RFC, a useful step forward would be to summarize all the existing RFCs and discussions from both GitHub and Zulip in a single document, so that it is easier to see the status quo, and the unresolved questions and blockers.

    I'm pretty sure I already did what you are referring by summarizing the discussion in the comment above that comment?

    Like I said, there isn't a clear design for what the feature should actually be; so honestly an RFC (or pre-rfc, or similar) - a "document", as you describe it, which I don't see why would be anything other than a RFC-like document. RFCs are for defining features with discussion.

    Jeremiah Senkpiel at 2024-03-19 22:40:00

  449. What are the blockers for stabilization right now? Does this need another RFC or can the initial implementation be added to stable?

    Émile Fugulin at 2024-05-06 22:50:46

  450. What are the blockers for stabilization right now? Does this need another RFC or can the initial implementation be added to stable?

    I would assume all issues would have to be fixed.

    For example, I'm still running into this one:

    Address issues with type inference (try { expr? }? currently requires an explicit type annotation somewhere).

    Houtamelo at 2024-12-09 19:14:06

  451. https://github.com/rust-lang/rfcs/pull/3721 is the latest update, IIRC.

    Jakub Beránek at 2024-12-09 19:26:48

  452. Forgive me if this has been asked before. Are the details for the semantics of nested try blocks fully worked out? I have wrote a trivial example, but it should illustrate the question I am asking.

    #![feature(try_blocks)]
    
    use std::{fs::File, io::{self, BufRead, BufReader}, num::ParseIntError};
    
    fn main() {
        let res: Result<u64, io::Error> = try {
            let file = File::options()
                .read(true)
                .open("number.txt")?;
            let mut buf = String::new();
            let res2: Result<u64, ParseIntError> = try {
                BufReader::new(file).read_line(&mut buf)?;
                buf.trim().parse()?
            };
            res2.expect("could not parse the number")
        };
        let num = res.expect("io error");
        println!("found num {num}");
    }
    

    This code does not compile, because the read_line function returns Return<_, io::Error>, but the inner try block expects a Result<_, ParseIntError>. This tells me that the current semantics are that the ? operator will only look at the innermost try block.

    While this example is a bit artificial, and I doubt whether nested try blocks should be considered, the same result can also be observed with a function that returns a Result which also contains a try block, as depicted in the following example.

    #![feature(try_blocks)]
    
    use std::{fs::File, io::{self, BufRead, BufReader}, num::ParseIntError};
    
    fn parse_from_file() -> Result<u64, io::Error> {
        let file = File::options()
            .read(true)
            .open("number.txt")?;
        let mut buf = String::new();
        let res2: Result<u64, ParseIntError> = try {
            BufReader::new(file).read_line(&mut buf)?;
            buf.trim().parse()?
        };
        Ok(res2.expect("could not parse the number"))
    }
    

    This also fails to compile. Perhaps the semantics for nested try blocks and the like could be that it will check each try block from innermost to outermost for matches with the ? operator, but this requires further discussion.

    Finally, I want to point out an issue with regards to the diagnostic message of the compile error of the above function. I have included it below. In this example, the reason the code fails to compile is because it cannot convert the io::Error to ParseIntError. This occurs within the try block, so the diagnostic message should point to the line with the try block, let res2: Result<u64, ParseIntError> = try {. However, it instead points to the function declaration. I am not sure where this bug should be reported, so I included it here.

    error[E0277]: `?` couldn't convert the error to `ParseIntError`
      --> main.rs:11:49
       |
    5  | fn parse_from_file() -> Result<u64, io::Error> {
       |                         ---------------------- expected `ParseIntError` because of this
    ...
    11 |         BufReader::new(file).read_line(&mut buf)?;
       |                              -------------------^ the trait `From<std::io::Error>` is not implemented for `ParseIntError`
       |                              |
       |                              this can't be annotated with `?` because it has type `Result<_, std::io::Error>`
       |
       = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0277`.
    

    James Petersen at 2025-04-18 23:35:51