New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First-class error handling with `?` and `catch` #243

Merged
merged 10 commits into from Feb 5, 2016

Conversation

Projects
None yet
@glaebhoerl
Contributor

glaebhoerl commented Sep 16, 2014

After a detour through thinking about first-class checked exceptions, I've now circled back around and convinced myself that @aturon's original idea of an ? operator for propagating exceptions is actually brilliant and the perfect middle path. But I also want try..catch.

CLICKME

@netvl

This comment has been minimized.

Show comment
Hide comment
@netvl

netvl Sep 16, 2014

This is really great, but I see a potential problem. The RFC does not say anything about how to translate different errors into each other, and I think this is very important. For example, your library may work with several other libraries, each providing its own kind of error, and sometimes you would want to pass these errors to the users of your library. The most natural way is to wrap them into your own error enum, with different variants for different kinds of original errors.

But under this proposal there is no support for such patterns at all. Frankly, I don't know even in the slightest how this can be done in syntactically light and convenient way and even if it is possible in principle. Exceptions in other language do not have this problem mainly due to subtyping and having special Exception supertype, and these features are not present in Rust.

I'm afraid that this can be a very common use-case, and, unfortunatley, ? operator won't help with it at all.

netvl commented Sep 16, 2014

This is really great, but I see a potential problem. The RFC does not say anything about how to translate different errors into each other, and I think this is very important. For example, your library may work with several other libraries, each providing its own kind of error, and sometimes you would want to pass these errors to the users of your library. The most natural way is to wrap them into your own error enum, with different variants for different kinds of original errors.

But under this proposal there is no support for such patterns at all. Frankly, I don't know even in the slightest how this can be done in syntactically light and convenient way and even if it is possible in principle. Exceptions in other language do not have this problem mainly due to subtyping and having special Exception supertype, and these features are not present in Rust.

I'm afraid that this can be a very common use-case, and, unfortunatley, ? operator won't help with it at all.

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon
Member

aturon commented Sep 16, 2014

@Ericson2314

This comment has been minimized.

Show comment
Hide comment
@Ericson2314

Ericson2314 Sep 17, 2014

Contributor

Ah, I was just thinking of making an RFC for "functional break" -- exactly your generalized return!

I am leery about most proposed syntactic sugars -- especially one as major as try..catch, and rather instead focus on using adding macros, or extending the macro system as necessary to give us what we want. (Hell, ideally I'd make bool an library-defined (enum) type, and even plain if..else a macro).

What I'd propose that people might actually agree with is simply adding the generalized break/return (Ideally one keyword could do everything, and the other would just be kept around for convenience), and making the try macro take an optional extra argument for a block lifetime:

match 'a: {
    try!('a, foo());
    try!('a, bar());
    Ok(()) // edit: added this so it would type check
} {
    None => ...,
     _ => ()
}

Not quite as pretty as try..catch, but on the other hand requires one only small addition to the language---and one that I'd argue more "rounds out" current features, since we already have loops as opposed to some system of mandatory tail calls, rather then delving it into new territory.

Besides my ascetic aversion to much syntactic sugar, I wonder whether the current demand for more control constructs will change with HKTs, and the design patterns they enable ;), and would vote for waiting until until we know the answer.

Contributor

Ericson2314 commented Sep 17, 2014

Ah, I was just thinking of making an RFC for "functional break" -- exactly your generalized return!

I am leery about most proposed syntactic sugars -- especially one as major as try..catch, and rather instead focus on using adding macros, or extending the macro system as necessary to give us what we want. (Hell, ideally I'd make bool an library-defined (enum) type, and even plain if..else a macro).

What I'd propose that people might actually agree with is simply adding the generalized break/return (Ideally one keyword could do everything, and the other would just be kept around for convenience), and making the try macro take an optional extra argument for a block lifetime:

match 'a: {
    try!('a, foo());
    try!('a, bar());
    Ok(()) // edit: added this so it would type check
} {
    None => ...,
     _ => ()
}

Not quite as pretty as try..catch, but on the other hand requires one only small addition to the language---and one that I'd argue more "rounds out" current features, since we already have loops as opposed to some system of mandatory tail calls, rather then delving it into new territory.

Besides my ascetic aversion to much syntactic sugar, I wonder whether the current demand for more control constructs will change with HKTs, and the design patterns they enable ;), and would vote for waiting until until we know the answer.

@nrc nrc assigned brson and aturon and unassigned brson Sep 18, 2014

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Oct 3, 2014

Member

@glaebhoerl

First of all, thanks for writing another beautiful and thorough RFC. It's always a pleasure to read these.

The ideas you're proposing help overcome one of my worries with the initial ? proposal, namely that it was tied to exiting the function. While this is the common case in Rust code today (probably due, in part, to try!), and you can always factor your code into smaller functions to make it work, the extra flexibility in this RFC seems both appealing and not very complicated.

I also wholeheartedly agree that this design finds a middle ground between completely implicit propagation (traditional exceptions) and completely explicit propagation (Result without try!). It recovers much of the ergonomics of implicit propagation, while keeping control-flow jumps explicit.

I think that throw and throws are promising as well. My main worry there is that throws somewhat muddies the integration with return types, for better or for worse. I suspect it could and should be integrated with closure type syntax, if we decide to include it.

I'm undecided on the Carrier question, in part because we're (still!) trying to finalize conventions around the use of Result and Option.

Have you looked at the error interoperation RFC? Part of the goal there was to allow different error types to be automatically converted when using try!. The reason that works, however, is that the function's return type (and hence the target error type) is always explicitly written. This property would no longer hold with try-catch blocks, so I'm not sure how or whether automatic conversion would apply there. I'd be interested to get your thoughts.

So, on the whole I'm pretty enthusiastic about these ideas, and I think that if we add ?, we should do so in the form proposed here.

However, as you know, at the moment we're setting a pretty high standard for RFC acceptance: we're relentlessly focusing on what is needed for a solid 1.0 release (e.g., backcompat hazards or proposals that are needed for overall usability or coherence for 1.0). After the release, we will of course start considering more "nice-to-have" features.

Personally, I feel that having a solid error-handling story is an important aspect of 1.0, which is part of why I've been pushing on various related aspects (both conventions and sugar). It's not clear to me whether try! is enough to make a good first impression, but we also have our hands rather full implementing already-accepted features. It might be possible to accept this RFC but explicitly not as a 1.0 blocker; I'm not sure, but I'll discuss it with the team.

Two final questions:

  • If we have a ? operator as proposed here for error handling, does that change your opinion about !? What if, independently, macro syntax was changed to use @ (which has been separately proposed; I know you're not fond of that, but hypothetically?)

  • Do you have thoughts on the minimal steps we can take pre-1.0 to ensure that we can implement this feature later? The main issue seems to be the try keyword, which of course conflicts with the try! macro. It's probably feasible (if confusing/ugly) to allow both, i.e. to treat ! as part of the identifier. Alternatively, we could rename try! -- any suggestions?

    Actually, one possibility would be to implement ? as it was originally proposed (i.e., limited to returning from the current function) and then dump or rename try!. Rust code written in that style would continue to feel idiomatic if/when the rest of this RFC was implemented.

(As an aside, Standard ML at least has a form of try/catch that yields an expression.)

Member

aturon commented Oct 3, 2014

@glaebhoerl

First of all, thanks for writing another beautiful and thorough RFC. It's always a pleasure to read these.

The ideas you're proposing help overcome one of my worries with the initial ? proposal, namely that it was tied to exiting the function. While this is the common case in Rust code today (probably due, in part, to try!), and you can always factor your code into smaller functions to make it work, the extra flexibility in this RFC seems both appealing and not very complicated.

I also wholeheartedly agree that this design finds a middle ground between completely implicit propagation (traditional exceptions) and completely explicit propagation (Result without try!). It recovers much of the ergonomics of implicit propagation, while keeping control-flow jumps explicit.

I think that throw and throws are promising as well. My main worry there is that throws somewhat muddies the integration with return types, for better or for worse. I suspect it could and should be integrated with closure type syntax, if we decide to include it.

I'm undecided on the Carrier question, in part because we're (still!) trying to finalize conventions around the use of Result and Option.

Have you looked at the error interoperation RFC? Part of the goal there was to allow different error types to be automatically converted when using try!. The reason that works, however, is that the function's return type (and hence the target error type) is always explicitly written. This property would no longer hold with try-catch blocks, so I'm not sure how or whether automatic conversion would apply there. I'd be interested to get your thoughts.

So, on the whole I'm pretty enthusiastic about these ideas, and I think that if we add ?, we should do so in the form proposed here.

However, as you know, at the moment we're setting a pretty high standard for RFC acceptance: we're relentlessly focusing on what is needed for a solid 1.0 release (e.g., backcompat hazards or proposals that are needed for overall usability or coherence for 1.0). After the release, we will of course start considering more "nice-to-have" features.

Personally, I feel that having a solid error-handling story is an important aspect of 1.0, which is part of why I've been pushing on various related aspects (both conventions and sugar). It's not clear to me whether try! is enough to make a good first impression, but we also have our hands rather full implementing already-accepted features. It might be possible to accept this RFC but explicitly not as a 1.0 blocker; I'm not sure, but I'll discuss it with the team.

Two final questions:

  • If we have a ? operator as proposed here for error handling, does that change your opinion about !? What if, independently, macro syntax was changed to use @ (which has been separately proposed; I know you're not fond of that, but hypothetically?)

  • Do you have thoughts on the minimal steps we can take pre-1.0 to ensure that we can implement this feature later? The main issue seems to be the try keyword, which of course conflicts with the try! macro. It's probably feasible (if confusing/ugly) to allow both, i.e. to treat ! as part of the identifier. Alternatively, we could rename try! -- any suggestions?

    Actually, one possibility would be to implement ? as it was originally proposed (i.e., limited to returning from the current function) and then dump or rename try!. Rust code written in that style would continue to feel idiomatic if/when the rest of this RFC was implemented.

(As an aside, Standard ML at least has a form of try/catch that yields an expression.)

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Oct 3, 2014

Contributor

OK, so I read this over. This is pretty cool stuff. If I'm not mistaken, the throws syntax (and the Carrier trait) are backwards compatible extensions, right? If so, I'd probably prefer to move slowly and leave those out for now.

There are some things I really like about this proposal:

It means that the role of the try keyword is more analogous to try as traditionally used (it defines the scope of error-handling, which try! obviously doesn't do).

This also seems to give you roughly all the things you might want to do in a fairly compact way:

  • foo? --> try!(foo)
  • try foo?.bar --> foo.map(bar)
  • try foo?.bar? --> foo.and_then(bar)
Contributor

nikomatsakis commented Oct 3, 2014

OK, so I read this over. This is pretty cool stuff. If I'm not mistaken, the throws syntax (and the Carrier trait) are backwards compatible extensions, right? If so, I'd probably prefer to move slowly and leave those out for now.

There are some things I really like about this proposal:

It means that the role of the try keyword is more analogous to try as traditionally used (it defines the scope of error-handling, which try! obviously doesn't do).

This also seems to give you roughly all the things you might want to do in a fairly compact way:

  • foo? --> try!(foo)
  • try foo?.bar --> foo.map(bar)
  • try foo?.bar? --> foo.and_then(bar)
@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Oct 3, 2014

Contributor

@aturon points out that for 1.0 staging we can add ? as a synonym for try! for now, and add try-catch keywords in later. This is probably worth nothing in the RFC.

Contributor

nikomatsakis commented Oct 3, 2014

@aturon points out that for 1.0 staging we can add ? as a synonym for try! for now, and add try-catch keywords in later. This is probably worth nothing in the RFC.

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Oct 3, 2014

Member

This also seems to give you roughly all the things you might want to do in a fairly compact way:

  • foo? --> try!(foo)
  • try foo?.bar --> foo.map(bar)
  • try foo?.bar? --> foo.and_then(bar)

Note that, in particular, this notation subsumes:

  • do notation when applied to the Error monad,
  • Swift's ?
  • try! that returns to the function boundary, which is not part of either of the two above.

I believe that even if we added monadic notation at some later time (which has many problems of its own), we would still profit from this specialized syntax for propagating and catching errors in a very lightweight way -- and a way that largely matches expectations when coming from a wide variety of other languages.

Member

aturon commented Oct 3, 2014

This also seems to give you roughly all the things you might want to do in a fairly compact way:

  • foo? --> try!(foo)
  • try foo?.bar --> foo.map(bar)
  • try foo?.bar? --> foo.and_then(bar)

Note that, in particular, this notation subsumes:

  • do notation when applied to the Error monad,
  • Swift's ?
  • try! that returns to the function boundary, which is not part of either of the two above.

I believe that even if we added monadic notation at some later time (which has many problems of its own), we would still profit from this specialized syntax for propagating and catching errors in a very lightweight way -- and a way that largely matches expectations when coming from a wide variety of other languages.

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Oct 3, 2014

Contributor

Also, I think I vaguely prefer the multiple catch arm syntax that @P1start suggested, since it resembles what other languages do, and because it unifies the two cases, though it obviously resembles match "less".

Contributor

nikomatsakis commented Oct 3, 2014

Also, I think I vaguely prefer the multiple catch arm syntax that @P1start suggested, since it resembles what other languages do, and because it unifies the two cases, though it obviously resembles match "less".

@arcto

This comment has been minimized.

Show comment
Hide comment
@arcto

arcto Oct 3, 2014

I really like the semantics and the ergonomics that this proposal would bring.

However, I can see this being used for a lot more than just exceptional failures. So I'm a bit unsure about the terminology and the naming of some of the constructs.

arcto commented Oct 3, 2014

I really like the semantics and the ergonomics that this proposal would bring.

However, I can see this being used for a lot more than just exceptional failures. So I'm a bit unsure about the terminology and the naming of some of the constructs.

@glaebhoerl

This comment has been minimized.

Show comment
Hide comment
@glaebhoerl

glaebhoerl Oct 4, 2014

Contributor

@Ericson2314 Feel free to submit a proposal for "functional break" if you like! I'm working on other things at the moment. (As discussed on discourse it seems like break would be the more appropriate choice, rather than return as I chose here.)


@aturon Thank you.

I think that throw and throws are promising as well. My main worry there is that throws somewhat muddies the integration with return types, for better or for worse. I suspect it could and should be integrated with closure type syntax, if we decide to include it.

Can you sketch how it might be implemented? I haven't thought about it deeply, but I wouldn't be surprised if it turned out to require higher-rank types for full generality. It's also not clear to me what the use case would be. (If you have a concrete fn that throws, it's polymorphic in the carrier, so it can be used at a closure type returning any concrete carrier. When or why would you want the closure itself to be polymorphic in the carrier?)

I'm undecided on the Carrier question, in part because we're (still!) trying to finalize conventions around the use of Result and Option.

Er, sorry: which Carrier question?

Have you looked at the error interoperation RFC? Part of the goal there was to allow different error types to be automatically converted when using try!. The reason that works, however, is that the function's return type (and hence the target error type) is always explicitly written. This property would no longer hold with try-catch blocks, so I'm not sure how or whether automatic conversion would apply there. I'd be interested to get your thoughts.

I have read it. With regards to the specific question as phrased, I suspect that the appropriate type for the try block can usually be inferred from the contents of the catch block. Do you have contrary examples? (But it's also not clear to me when you would want to do this -- wrapping in Box<Error> is something you would do to hide the specific error types used by upstream dependencies from downstream clients. But that's if you are propagating the errors. If you're handling them yourself with try..catch, why wouldn't you just inspect them directly? Why would you want to hide their types from yourself?)

Perhaps more importantly, the design as formulated in this RFC would preclude that kind of automatic conversion happening with the ? operator. Personally, I think that's a good thing. I recognize that the ergonomics of interfacing different errors types are important, but I would be deeply uncomfortable with baking this kind of ad-hoc special casing into the guts of the language. It's not even clear that this is the best way to do it, and having strong laws and ability to reason about code is much more important. ("Those who would give up essential Guarantees, to purchase a little temporary Convenience, deserve neither Guarantees nor Convenience." --Ben Franklin) I think a reasonable path forward would be to have a macro, separate from ?, which does do the automatic conversion (for instance rethrow!; I'm not sure if that's the best name). Then ? would have nice properties, the ergonomics of error-conversion would not be worse than proposed in the error interoperation RFC (which also assumed a macro), and it would be clear where automatic conversion may or may not happen.

However, as you know, at the moment we're setting a pretty high standard for RFC acceptance: we're relentlessly focusing on what is needed for a solid 1.0 release [...]

That's fine; this RFC was primarily intended to inform the ongoing debate about error handling. (But as far as I'm aware, an accepted RFC also does not imply that it has to, or will be, implemented before 1.0.)

Do you have thoughts on the minimal steps we can take pre-1.0 to ensure that we can implement this feature later?

I agree that the best approach would be to replace the try! macro with an ? operator restricted to the Result type and returning from the current function. This is a strict subset of the functionality described by the RFC, so I don't think it requires modifying the RFC in any way; it can just be considered "partially implemented" at that point.

If we have a ? operator as proposed here for error handling, does that change your opinion about !? What if, independently, macro syntax was changed to use @ (which has been separately proposed; I know you're not fond of that, but hypothetically?)

It doesn't. My opinion about the ! operator is that it's "probably a bad idea". (It has a kind of nice symmetry with ?, but that doesn't make it wise.) More importantly however, I think that adding it would be premature and that doing so is not supported by the weight of the available evidence. The idea that convention-following APIs would be too cumbersome to use without it is highly speculative, especially given that other ergonomically significant features, such as in this RFC, are not yet available. There is also at least one indication that it is possible to avoid the need for it entirely. If, after half a year or so, experience shows that living without ! is still too painful, then a compelling case could possibly be made. But not now. (And I would be surprised.)

The fact that it also entails losing the ! syntax for macros is just anti-icing on the anti-cake.

(If we decide that we don't want an ! operator after all, can we then have it back for macros? My guess is no, because having two equivalent syntaxes is undesirable.)


@nikomatsakis

try foo?.bar --> foo.map(bar)
try foo?.bar? --> foo.and_then(bar)

I can't help but notice that you left off the braces. It would be nice to allow this, but would it not run into the same ambiguities as if..else? (This might also be connected to the choice of catch syntax.)

Also, I think I vaguely prefer the multiple catch arm syntax that @P1start suggested, since it resembles what other languages do, and because it unifies the two cases, though it obviously resembles match "less".

As in my comment to @P1start, I think the fact that it unifies the cases is nice, but having similar things look similar and different things look different feels more important.


@arcto

However, I can see this being used for a lot more than just exceptional failures. So I'm a bit unsure about the terminology and the naming of some of the constructs.

I do think we can dispense with some of the stigmas and mythology surrounding exception handling in other languages, e.g. the hair-splitting about the meaning of "truly exceptional circumstances" and so on, which are due to the fact that they have to choose between error codes and exceptions, while here they are unified, and that their exceptions have various significant and undesirable aspects (e.g. not being tracked in the type system), while these don't. It can be used as just another control flow construct, and I think that's fine. Familiar syntax is still worthwhile. (See also e.g. enum for ADTs.)

Contributor

glaebhoerl commented Oct 4, 2014

@Ericson2314 Feel free to submit a proposal for "functional break" if you like! I'm working on other things at the moment. (As discussed on discourse it seems like break would be the more appropriate choice, rather than return as I chose here.)


@aturon Thank you.

I think that throw and throws are promising as well. My main worry there is that throws somewhat muddies the integration with return types, for better or for worse. I suspect it could and should be integrated with closure type syntax, if we decide to include it.

Can you sketch how it might be implemented? I haven't thought about it deeply, but I wouldn't be surprised if it turned out to require higher-rank types for full generality. It's also not clear to me what the use case would be. (If you have a concrete fn that throws, it's polymorphic in the carrier, so it can be used at a closure type returning any concrete carrier. When or why would you want the closure itself to be polymorphic in the carrier?)

I'm undecided on the Carrier question, in part because we're (still!) trying to finalize conventions around the use of Result and Option.

Er, sorry: which Carrier question?

Have you looked at the error interoperation RFC? Part of the goal there was to allow different error types to be automatically converted when using try!. The reason that works, however, is that the function's return type (and hence the target error type) is always explicitly written. This property would no longer hold with try-catch blocks, so I'm not sure how or whether automatic conversion would apply there. I'd be interested to get your thoughts.

I have read it. With regards to the specific question as phrased, I suspect that the appropriate type for the try block can usually be inferred from the contents of the catch block. Do you have contrary examples? (But it's also not clear to me when you would want to do this -- wrapping in Box<Error> is something you would do to hide the specific error types used by upstream dependencies from downstream clients. But that's if you are propagating the errors. If you're handling them yourself with try..catch, why wouldn't you just inspect them directly? Why would you want to hide their types from yourself?)

Perhaps more importantly, the design as formulated in this RFC would preclude that kind of automatic conversion happening with the ? operator. Personally, I think that's a good thing. I recognize that the ergonomics of interfacing different errors types are important, but I would be deeply uncomfortable with baking this kind of ad-hoc special casing into the guts of the language. It's not even clear that this is the best way to do it, and having strong laws and ability to reason about code is much more important. ("Those who would give up essential Guarantees, to purchase a little temporary Convenience, deserve neither Guarantees nor Convenience." --Ben Franklin) I think a reasonable path forward would be to have a macro, separate from ?, which does do the automatic conversion (for instance rethrow!; I'm not sure if that's the best name). Then ? would have nice properties, the ergonomics of error-conversion would not be worse than proposed in the error interoperation RFC (which also assumed a macro), and it would be clear where automatic conversion may or may not happen.

However, as you know, at the moment we're setting a pretty high standard for RFC acceptance: we're relentlessly focusing on what is needed for a solid 1.0 release [...]

That's fine; this RFC was primarily intended to inform the ongoing debate about error handling. (But as far as I'm aware, an accepted RFC also does not imply that it has to, or will be, implemented before 1.0.)

Do you have thoughts on the minimal steps we can take pre-1.0 to ensure that we can implement this feature later?

I agree that the best approach would be to replace the try! macro with an ? operator restricted to the Result type and returning from the current function. This is a strict subset of the functionality described by the RFC, so I don't think it requires modifying the RFC in any way; it can just be considered "partially implemented" at that point.

If we have a ? operator as proposed here for error handling, does that change your opinion about !? What if, independently, macro syntax was changed to use @ (which has been separately proposed; I know you're not fond of that, but hypothetically?)

It doesn't. My opinion about the ! operator is that it's "probably a bad idea". (It has a kind of nice symmetry with ?, but that doesn't make it wise.) More importantly however, I think that adding it would be premature and that doing so is not supported by the weight of the available evidence. The idea that convention-following APIs would be too cumbersome to use without it is highly speculative, especially given that other ergonomically significant features, such as in this RFC, are not yet available. There is also at least one indication that it is possible to avoid the need for it entirely. If, after half a year or so, experience shows that living without ! is still too painful, then a compelling case could possibly be made. But not now. (And I would be surprised.)

The fact that it also entails losing the ! syntax for macros is just anti-icing on the anti-cake.

(If we decide that we don't want an ! operator after all, can we then have it back for macros? My guess is no, because having two equivalent syntaxes is undesirable.)


@nikomatsakis

try foo?.bar --> foo.map(bar)
try foo?.bar? --> foo.and_then(bar)

I can't help but notice that you left off the braces. It would be nice to allow this, but would it not run into the same ambiguities as if..else? (This might also be connected to the choice of catch syntax.)

Also, I think I vaguely prefer the multiple catch arm syntax that @P1start suggested, since it resembles what other languages do, and because it unifies the two cases, though it obviously resembles match "less".

As in my comment to @P1start, I think the fact that it unifies the cases is nice, but having similar things look similar and different things look different feels more important.


@arcto

However, I can see this being used for a lot more than just exceptional failures. So I'm a bit unsure about the terminology and the naming of some of the constructs.

I do think we can dispense with some of the stigmas and mythology surrounding exception handling in other languages, e.g. the hair-splitting about the meaning of "truly exceptional circumstances" and so on, which are due to the fact that they have to choose between error codes and exceptions, while here they are unified, and that their exceptions have various significant and undesirable aspects (e.g. not being tracked in the type system), while these don't. It can be used as just another control flow construct, and I think that's fine. Familiar syntax is still worthwhile. (See also e.g. enum for ADTs.)

It is possible to carry the exception handling analogy further and also add
`throw` and `throws` constructs.
`throw` is very simple: `throw EXPR` is essentially the same thing as

This comment has been minimized.

@reem

reem Oct 7, 2014

I want to like this a lot (it's much cleaner syntactically) but I fear the additional (apparent, if not real) complexity.

@reem

reem Oct 7, 2014

I want to like this a lot (it's much cleaner syntactically) but I fear the additional (apparent, if not real) complexity.

@hatahet

This comment has been minimized.

Show comment
Hide comment
@hatahet

hatahet Oct 7, 2014

I came across a couple of articles and thought they might be worth considering:

I realize though this is not 100% similar to C++'s (unchecked) or Java's (checked) exceptions.

hatahet commented Oct 7, 2014

I came across a couple of articles and thought they might be worth considering:

I realize though this is not 100% similar to C++'s (unchecked) or Java's (checked) exceptions.

@brson

This comment has been minimized.

Show comment
Hide comment
@brson

brson Oct 8, 2014

Contributor

While I think this is an interesting and novel proposal, I am concerned about re-purposing the exception handling terminology ('try', 'catch', 'throw', 'exception').

  • Exception handling has baggage, and people will draw conclusions based on the words alone.
  • The mechanism is different from typical exceptions, has different performance characteristics and behavior.
  • Rust already also includes much of the traditional exception handling mechanism, but in a non-traditional form, under a different name (panic), so calling something else 'exceptions' makes the issue rather muddy.
  • Finally, this is such a cool idea that we might want to completely own it.
Contributor

brson commented Oct 8, 2014

While I think this is an interesting and novel proposal, I am concerned about re-purposing the exception handling terminology ('try', 'catch', 'throw', 'exception').

  • Exception handling has baggage, and people will draw conclusions based on the words alone.
  • The mechanism is different from typical exceptions, has different performance characteristics and behavior.
  • Rust already also includes much of the traditional exception handling mechanism, but in a non-traditional form, under a different name (panic), so calling something else 'exceptions' makes the issue rather muddy.
  • Finally, this is such a cool idea that we might want to completely own it.
@suhr

This comment has been minimized.

Show comment
Hide comment
@suhr

suhr Oct 8, 2014

Will it make a mess when HKT is added to the language?

suhr commented Oct 8, 2014

Will it make a mess when HKT is added to the language?

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Oct 8, 2014

Contributor

@glaebhoerl you're correct about try {} vs try expr. Having to put braces is kind of a downer, but I agree that to do otherwise is inconsistent with our if/else story.

@brson interesting point. I wonder what would be a compelling alternative set of terms.

Contributor

nikomatsakis commented Oct 8, 2014

@glaebhoerl you're correct about try {} vs try expr. Having to put braces is kind of a downer, but I agree that to do otherwise is inconsistent with our if/else story.

@brson interesting point. I wonder what would be a compelling alternative set of terms.

@arthurprs

This comment has been minimized.

Show comment
Hide comment
@arthurprs

arthurprs Oct 8, 2014

I second @brson thoughts. If this gets incorporated it'd be best to move away from "exception handling"-ish descriptions and stick with "error handling"

arthurprs commented Oct 8, 2014

I second @brson thoughts. If this gets incorporated it'd be best to move away from "exception handling"-ish descriptions and stick with "error handling"

@huonw

This comment has been minimized.

Show comment
Hide comment
@huonw

huonw Oct 8, 2014

Member

I can't help but notice that you left off the braces. It would be nice to allow this, but would it not run into the same ambiguities as if..else? (This might also be connected to the choice of catch syntax.)

@glaebhoerl you're correct about try {} vs try expr. Having to put braces is kind of a downer, but I agree that to do otherwise is inconsistent with our if/else story.

I'm missing something here: don't we require braces because it would be if cond expr else, that is, there's two adjacent expressions and that doesn't work. For try (AIUI) it is just try expr catch i.e. one expression and so perfectly OK from a grammar stand-point?

Also, it's not that inconsistent with if etc; we don't require delimiters around the condition of a if/while, or match head.

Member

huonw commented Oct 8, 2014

I can't help but notice that you left off the braces. It would be nice to allow this, but would it not run into the same ambiguities as if..else? (This might also be connected to the choice of catch syntax.)

@glaebhoerl you're correct about try {} vs try expr. Having to put braces is kind of a downer, but I agree that to do otherwise is inconsistent with our if/else story.

I'm missing something here: don't we require braces because it would be if cond expr else, that is, there's two adjacent expressions and that doesn't work. For try (AIUI) it is just try expr catch i.e. one expression and so perfectly OK from a grammar stand-point?

Also, it's not that inconsistent with if etc; we don't require delimiters around the condition of a if/while, or match head.

@arcto

This comment has been minimized.

Show comment
Hide comment
@arcto

arcto Oct 8, 2014

I'm not going to defend this position, but short-circuiting by using an early break/return or ?-operator can be seen as a more general feature than both error handling and exception handling. Suppose, for instance, that the "carrier" is an Option. A value of None is not necessarily an error.

arcto commented Oct 8, 2014

I'm not going to defend this position, but short-circuiting by using an early break/return or ?-operator can be seen as a more general feature than both error handling and exception handling. Suppose, for instance, that the "carrier" is an Option. A value of None is not necessarily an error.

@glaebhoerl

This comment has been minimized.

Show comment
Hide comment
@glaebhoerl

glaebhoerl Oct 8, 2014

Contributor

@huonw IIRC we require braces to avoid the common mistake from other C-like languages where an else clause is interpreted by the compiler as belonging to a different if than which the author intended.

@brson

  • Exception handling has baggage, and people will draw conclusions based on the words alone.

This is a reasonable point. I was thinking of aiming for the title of "the language that finally got exceptions right".

The goal of this naming scheme is to provide a good intuition for the constructs. "It works like try-catch in other languages, except you need an explicit question mark to propagate". (Whereas otherwise it could end up being "that weird error handling construct that Rust has".) I think there's enough variety among exception handling implementations in existing languages that our deviations can fit under the same roof. (Haskell also uses exception handling terminology for their implementation, which has much more in common with this one than with others.)

But even that is probably overexplaining it. They have these names because the try-catch of existing languages is where the idea came from in the first place.

(Just as a thought experiment for anyone reading -- do you think it would have been easier or harder to grok the intended meaning and usage of these proposed constructs if they had been presented using different vocabulary, not connected to exception handling?)

  • The mechanism is different from typical exceptions, has different performance characteristics and behavior.
  • Rust already also includes much of the traditional exception handling mechanism, but in a non-traditional form, under a different name (panic), so calling something else 'exceptions' makes the issue rather muddy.

I think people are more attuned to meaning than to mechanism. If they're not meant to be caught then they're not really exceptions (accordingly, we call them panics), even if it involves unwinding.

All that said this is all very theoretical. My feeling is that trying to go with different names just for the sake of being different is likely to end up being more confusing, not less. But this could be assessed much more straightforwardly for a concrete set of alternative names.

Contributor

glaebhoerl commented Oct 8, 2014

@huonw IIRC we require braces to avoid the common mistake from other C-like languages where an else clause is interpreted by the compiler as belonging to a different if than which the author intended.

@brson

  • Exception handling has baggage, and people will draw conclusions based on the words alone.

This is a reasonable point. I was thinking of aiming for the title of "the language that finally got exceptions right".

The goal of this naming scheme is to provide a good intuition for the constructs. "It works like try-catch in other languages, except you need an explicit question mark to propagate". (Whereas otherwise it could end up being "that weird error handling construct that Rust has".) I think there's enough variety among exception handling implementations in existing languages that our deviations can fit under the same roof. (Haskell also uses exception handling terminology for their implementation, which has much more in common with this one than with others.)

But even that is probably overexplaining it. They have these names because the try-catch of existing languages is where the idea came from in the first place.

(Just as a thought experiment for anyone reading -- do you think it would have been easier or harder to grok the intended meaning and usage of these proposed constructs if they had been presented using different vocabulary, not connected to exception handling?)

  • The mechanism is different from typical exceptions, has different performance characteristics and behavior.
  • Rust already also includes much of the traditional exception handling mechanism, but in a non-traditional form, under a different name (panic), so calling something else 'exceptions' makes the issue rather muddy.

I think people are more attuned to meaning than to mechanism. If they're not meant to be caught then they're not really exceptions (accordingly, we call them panics), even if it involves unwinding.

All that said this is all very theoretical. My feeling is that trying to go with different names just for the sake of being different is likely to end up being more confusing, not less. But this could be assessed much more straightforwardly for a concrete set of alternative names.

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Oct 8, 2014

Contributor

On Wed, Oct 08, 2014 at 06:04:41AM -0700, Huon Wilson wrote:

@glaebhoerl you're correct about try {} vs try expr. Having to put braces is kind of a downer, but I agree that to do otherwise is inconsistent with our if/else story.

I'm missing something here: don't we require braces because it would be if cond expr else, that is, there's two adjacent expressions and that doesn't work. For try (AIUI) it is just try expr catch i.e. one expression and so perfectly OK from a grammar stand-point?

Well, there are multiple reasons to require braces. One of them is that it lets you drop the parens, yes. The other is that it avoids ambiguity for cases like

if (cond1)
    if (cond2)
        something
else
    something_else

in this case, the else is associated with the inner if, despite what the indentation suggests.

You can certainly construct analogous situations with the propose try/catch. One fix would be to permit dropping the {} if there is no catch.

Contributor

nikomatsakis commented Oct 8, 2014

On Wed, Oct 08, 2014 at 06:04:41AM -0700, Huon Wilson wrote:

@glaebhoerl you're correct about try {} vs try expr. Having to put braces is kind of a downer, but I agree that to do otherwise is inconsistent with our if/else story.

I'm missing something here: don't we require braces because it would be if cond expr else, that is, there's two adjacent expressions and that doesn't work. For try (AIUI) it is just try expr catch i.e. one expression and so perfectly OK from a grammar stand-point?

Well, there are multiple reasons to require braces. One of them is that it lets you drop the parens, yes. The other is that it avoids ambiguity for cases like

if (cond1)
    if (cond2)
        something
else
    something_else

in this case, the else is associated with the inner if, despite what the indentation suggests.

You can certainly construct analogous situations with the propose try/catch. One fix would be to permit dropping the {} if there is no catch.

@brson

This comment has been minimized.

Show comment
Hide comment
@brson

brson Oct 8, 2014

Contributor

@glaebhoerl

(Just as a thought experiment for anyone reading -- do you think it would have been easier or harder to grok the intended meaning and usage of these proposed constructs if they had been presented using different vocabulary, not connected to exception handling?)

I do think try/catch provides the right intuition, and that's an argument that has won some (not all) naming debates in Rust in the past (and we even have several things called 'try' because it's such an intuitive concept). The word 'exception' is definitely the most worrisome here, though not having 'exceptions' but having try/catch requires some explanation.

We're probably going to end up with a long FAQ entry explaining the subtleties of exception and error handling as relates to Rust no matter what :)

Contributor

brson commented Oct 8, 2014

@glaebhoerl

(Just as a thought experiment for anyone reading -- do you think it would have been easier or harder to grok the intended meaning and usage of these proposed constructs if they had been presented using different vocabulary, not connected to exception handling?)

I do think try/catch provides the right intuition, and that's an argument that has won some (not all) naming debates in Rust in the past (and we even have several things called 'try' because it's such an intuitive concept). The word 'exception' is definitely the most worrisome here, though not having 'exceptions' but having try/catch requires some explanation.

We're probably going to end up with a long FAQ entry explaining the subtleties of exception and error handling as relates to Rust no matter what :)

@repax

This comment has been minimized.

Show comment
Hide comment
@repax

repax Feb 5, 2016

Or like this:

if let Err(e) = catch {
    ...
} {
    handle(e);
}

repax commented Feb 5, 2016

Or like this:

if let Err(e) = catch {
    ...
} {
    handle(e);
}

nikomatsakis added a commit to nikomatsakis/rfcs that referenced this pull request Feb 5, 2016

@logannc

This comment has been minimized.

Show comment
Hide comment
@logannc

logannc Feb 5, 2016

Well, isn't that nifty. If the catch block is big that might look odd
having ... } { ... but its sort of nice that there is nothing else
required.
On Feb 5, 2016 1:43 PM, "Eduard-Mihai Burtescu" notifications@github.com
wrote:

catch { ... }.unwrap_or(|v| match v { ... })

match catch { ... } { Ok(x) => x, Err(...) => ..., ... } might look
better in some cases, fwiw.


Reply to this email directly or view it on GitHub
#243 (comment).

logannc commented Feb 5, 2016

Well, isn't that nifty. If the catch block is big that might look odd
having ... } { ... but its sort of nice that there is nothing else
required.
On Feb 5, 2016 1:43 PM, "Eduard-Mihai Burtescu" notifications@github.com
wrote:

catch { ... }.unwrap_or(|v| match v { ... })

match catch { ... } { Ok(x) => x, Err(...) => ..., ... } might look
better in some cases, fwiw.


Reply to this email directly or view it on GitHub
#243 (comment).

@glaebhoerl

This comment has been minimized.

Show comment
Hide comment
@glaebhoerl

glaebhoerl Feb 5, 2016

Contributor

@nikomatsakis So where's the tracking issue? :)

@eddyb @logannc Wouldn't that run afoul of the same parser shenanigans which prevent using struct literals post-match? I recall some discussion upthread to that effect. I assume you could solve it by putting parentheses around the catch { }...

Contributor

glaebhoerl commented Feb 5, 2016

@nikomatsakis So where's the tracking issue? :)

@eddyb @logannc Wouldn't that run afoul of the same parser shenanigans which prevent using struct literals post-match? I recall some discussion upthread to that effect. I assume you could solve it by putting parentheses around the catch { }...

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Feb 5, 2016

Contributor

@glaebhoerl sorry, still catching up on the "paperwork" side here :) will open the tracking issue in a second.

Contributor

nikomatsakis commented Feb 5, 2016

@glaebhoerl sorry, still catching up on the "paperwork" side here :) will open the tracking issue in a second.

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Feb 5, 2016

Contributor

Regarding catch as a match discriminant, I think it will run afoul of the rules regarding match expressions without parentheses, though the if let form would work fine (but of course is slightly different in its effect).

(I am presuming we are going to add catch as a contextual keyword; if it were a keyword from the start, we could potentially allow it as a match argument I guess.)

Contributor

nikomatsakis commented Feb 5, 2016

Regarding catch as a match discriminant, I think it will run afoul of the rules regarding match expressions without parentheses, though the if let form would work fine (but of course is slightly different in its effect).

(I am presuming we are going to add catch as a contextual keyword; if it were a keyword from the start, we could potentially allow it as a match argument I guess.)

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Feb 5, 2016

Contributor

Tracking issue: rust-lang/rust#31436

Contributor

nikomatsakis commented Feb 5, 2016

Tracking issue: rust-lang/rust#31436

@nikomatsakis nikomatsakis merged commit 2daab80 into rust-lang:master Feb 5, 2016

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Feb 5, 2016

Contributor

OK, merged (with some edits). If anybody sees any place that I missed a reference to try/catch or something like that, let me know.

Contributor

nikomatsakis commented Feb 5, 2016

OK, merged (with some edits). If anybody sees any place that I missed a reference to try/catch or something like that, let me know.

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Feb 5, 2016

Contributor

See in particular: 94390a2

Contributor

nikomatsakis commented Feb 5, 2016

See in particular: 94390a2

@aturon aturon changed the title from Trait-based exception handling to First-class error handling with `?` and `catch` Feb 5, 2016

@durka

This comment has been minimized.

Show comment
Hide comment
@durka

durka Feb 5, 2016

Contributor

@nikomatsakis search for "try block" to find a bunch of missed replacements. And the "Laws" seem like they'll need to be rewritten.

Contributor

durka commented Feb 5, 2016

@nikomatsakis search for "try block" to find a bunch of missed replacements. And the "Laws" seem like they'll need to be rewritten.

@nrc

This comment has been minimized.

Show comment
Hide comment
@nrc

nrc May 18, 2016

Member

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). If the trait is still contentious after experimentation and discussion, then we can open an RFC (the language team felt this did not need to be the default path though). See this discuss thread for more details.

Member

nrc commented May 18, 2016

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). If the trait is still contentious after experimentation and discussion, then we can open an RFC (the language team felt this did not need to be the default path though). See this discuss thread for more details.

@bugaevc bugaevc referenced this pull request May 29, 2016

Closed

Async IO #1081

@MatejLach MatejLach referenced this pull request Aug 7, 2016

Merged

Change decoding functions #3

@sciyoshi

This comment has been minimized.

Show comment
Hide comment
@sciyoshi

sciyoshi Sep 10, 2016

I didn't see any discussion in this thread about the possibility of reserving ? as syntax for nullable types, i.e. Option. I feel like Option (and Result) are common enough that they might eventually warrant shortened syntax, like u32? rather than Option<u32>. Would that still be possible with this RFC?

sciyoshi commented Sep 10, 2016

I didn't see any discussion in this thread about the possibility of reserving ? as syntax for nullable types, i.e. Option. I feel like Option (and Result) are common enough that they might eventually warrant shortened syntax, like u32? rather than Option<u32>. Would that still be possible with this RFC?

@glaebhoerl

This comment has been minimized.

Show comment
Hide comment
@glaebhoerl

glaebhoerl Sep 11, 2016

Contributor

@sciyoshi Yes, types and values have separate syntax.

Contributor

glaebhoerl commented Sep 11, 2016

@sciyoshi Yes, types and values have separate syntax.

@est31 est31 referenced this pull request Sep 16, 2016

Closed

Longer question marks RFC #1737

withoutboats pushed a commit to withoutboats/rfcs that referenced this pull request Jan 15, 2017

Merge pull request #243 from alexbool/master
futures-cpupool: enrich `Builder` with thread name prefix
@KalitaAlexey

This comment has been minimized.

Show comment
Hide comment
@KalitaAlexey

KalitaAlexey Feb 27, 2017

What is going to be implemented for the feature?
? is implemented. catch is being implemented. How about match?

KalitaAlexey commented Feb 27, 2017

What is going to be implemented for the feature?
? is implemented. catch is being implemented. How about match?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment