Skip to content
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

extend `?` to operate over other types #1859

Merged
merged 10 commits into from May 30, 2017

Conversation

@nikomatsakis
Copy link
Contributor

commented Jan 19, 2017

Introduce a trait Try for customizing the behavior of the ? operator when applied to types other than Result.

Fixes #1718.

Rendered: https://github.com/rust-lang/rfcs/blob/master/text/1859-try-trait.md

[edited to link to final rendered version]

@nikomatsakis nikomatsakis added the T-lang label Jan 19, 2017

@nikomatsakis

This comment has been minimized.

Copy link
Contributor Author

commented Jan 19, 2017

I don't know how to make a "rendered" link anymore. =)

@withoutboats

This comment has been minimized.

@@ -0,0 +1,478 @@
- Feature Name: (fill me in with a unique ident, my_awesome_feature)
- Start Date: (fill me in with today's date, YYYY-MM-DD)

This comment has been minimized.

Copy link
@shepmaster

shepmaster Jan 19, 2017

Member

Did you want to fill these in? ^^

This comment has been minimized.

Copy link
@nikomatsakis

nikomatsakis Jan 20, 2017

Author Contributor

done.

### What to name the trait

A number of names have been proposed for this trait. The original name
was `Carrier`, as the trait "carrier" an error value. A proposed

This comment has been minimized.

Copy link
@shepmaster

shepmaster Jan 19, 2017

Member

I'm unclear if

as the trait "carrier" an error value

Is a typo or an example of why the previous name was poor. I'd have expected "carries".

This comment has been minimized.

Copy link
@nikomatsakis

nikomatsakis Jan 20, 2017

Author Contributor

That is a typo.

This comment has been minimized.

Copy link
@nikomatsakis

nikomatsakis Jan 20, 2017

Author Contributor

fixed.

@shepmaster

This comment has been minimized.

Copy link
Member

commented Jan 19, 2017

Thank you @nikomatsakis! I am excited to watch the progress of this RFC!

This more general behavior can be taught at the same time as the ? operator

Depending on the level of detail envisioned, I'd argue that doing this may be too much. Explicit return types already seem different enough from the exception-like handling that many newcomers are used to; expanding the universe immediately by making it generic might be overwhelming.

I'd lean towards a throwaway sentence that indicates that the behavior of ? can be overloaded, similar to other operators, with a link to the API docs. That is a reasonable location to have detailed implementation instructions.

The Option type includes an impl as follows

I don't have a strong opinion on implementing Try for Option, but I'd hate to see the RFC get bogged down on that point. I'd much rather have the trait at all so I can implement it for my own types. If the tide turns against impl Try for Option, I'd hope that the trait itself perseveres.

@shepmaster

This comment has been minimized.

Copy link
Member

commented Jan 19, 2017

TL;DR: I'm OK with Result as used in this RFC

The Try trait uses Result as its return type.

In the lead-up to the RFC, I expressed mild concern at the use of Result here. I do weakly agree with the RFC: the use of Result is at least in the correct semantic ballpark.

My main concern is against using Result as a generic "a or b" container type solely because it's what we already have in the standard library. I don't think that concern is an issue here, but it's near the line.

@carols10cents

This comment has been minimized.

Copy link
Member

commented Jan 19, 2017

A very common problem that new rustaceans hit is trying to use ? in main or some other method that doesn't return Result. The current error message that you get says the trait bound `(): std::ops::Carrier` is not satisfied which is not particularly helpful. If this RFC gets accepted and implemented, will the error message remain as it is and just say "Try" instead of "Carrier"?

I totally understand if this RFC is orthogonal to the issue I'm interested in :)

EDIT: Aha, found the discussion more focused on ? in main, was looking at RFCs and issues and couldn't find it a minute ago. Seems like that will be a totally separate RFC than this one, correct?

@clarfon

This comment has been minimized.

Copy link
Contributor

commented Jan 20, 2017

I personally think that TryResult would be better as:

enum TryResult<T, R> {
    Yield(T),
    Return(R),
}

Than Abrupt and Ok, because this seems a bit more natural. It also allows the variants to be exported in prelude whereas Ok would conflict with Result::Ok.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor Author

commented Jan 20, 2017

@carols10cents

If this RFC gets accepted and implemented, will the error message remain as it is and just say "Try" instead of "Carrier"?

I agree we need to correct the error message. I also think that it's worth thinking and drafting error messages here, in this RFC -- too often we leave it as an afterthought. Do you have suggestions for the wording? That said, when you wrote "Seems like that will be a totally separate RFC than this one, correct?", I'd say that this is the overkill. That is, we can (and do!) improve error messages without the need for RFCs.

I would think the message would be something like:

cannot use the ? operator in a function that returns ()

We could also go further and analyze the type to which ? is applied and figure out the set of legal return types for main. So if you were doing foo.write()? (i.e., applying ? to an io::Result), then we could offer a suggestion like "consider changing the return type to Result<(), io::Error>" or perhaps just "consider changing the return type to a Result"). If we really wanted to go crazy, though, we'd maybe some further annotations to suggest something like io::Result<()>, but that would require some form of annotation on io::Error that links to the io::Result alias, I imagine. It seems like starting with suggesting that the function return a Result would be so much better than we have.

I can add some text to this effect.

UPDATE: Done.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor Author

commented Jan 20, 2017

@shepmaster @clarcharr Regarding the use of Result: I have explained my reasons in the RFC. I don't feel extremely strongly about this and I'm certainly open to adding a new type. Result I think is what I'd prefer because (as I wrote) it seems reasonably appropriate and I don't feel a strong need for a throw-away enum here. I guess I'd like the feedback from @rust-lang/libs in general about this point.

I don't have a strong opinion on implementing Try for Option, but I'd hate to see the RFC get bogged down on that point. I'd much rather have the trait at all so I can implement it for my own types. If the tide turns against impl Try for Option, I'd hope that the trait itself perseveres.

Indeed, I would remove Option if forced, but I have heard from many people who strongly want support for Option -- myself included =) -- and hence I am inclined to keep it in. The main objection I have heard is that interconversion between Option and Result is semantically loose, and this RFC avoids that.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor Author

commented Jan 20, 2017

Ah, @clarcharr, regarding the names Yield and Return, I considered Yield as a name, but avoided it for fear of confusion with coroutines and generators, which are also under consideration. Return is perhaps better than Abrupt, except that the value is not always returned, it may be propagated to the innermost enclosing catch (if only that were implemented...).

@clarfon

This comment has been minimized.

Copy link
Contributor

commented Jan 20, 2017

@nikomatsakis Personally I think that throwway types are one of the things that makes Rust really powerful. In projects I tend to use enums extensively even if they only have two variants, because I think it makes things more clear, and they're not as equivalent to bool and usize like in C. I don't think that the argument for using methods on Result is very compelling imho because I can't see many implementations using them. Could easily be proven wrong, though.

As far as Yield goes, maybe Continue?

@carols10cents

This comment has been minimized.

Copy link
Member

commented Jan 20, 2017

I agree we need to correct the error message. I also think that it's worth thinking and drafting error messages here, in this RFC -- too often we leave it as an afterthought. Do you have suggestions for the wording? That said, when you wrote "Seems like that will be a totally separate RFC than this one, correct?", I'd say that this is the overkill. That is, we can (and do!) improve error messages without the need for RFCs.

Oh, I guess in my head improving the error message seems tied up with this discussion about treating ? differently in main, I'm glad you're in favor of thinking about the error message here though!

I would think the message would be something like:

cannot use the ? operator in a function that returns ()

That would be better than what's currently there, definitely. I'd also like a companion note that makes a recommendation like "Consider extracting the code where you want to use the ? into a function that returns Result and using a match statement here to handle the return value from the new function". I realize that's long.

We could also go further and analyze the type to which ? is applied and figure out the set of legal return types for main. So if you were doing foo.write()? (i.e., applying ? to an io::Result), then we could offer a suggestion like "consider changing the return type to Result<(), io::Error>" or perhaps just "consider changing the return type to a Result"). If we really wanted to go crazy, though, we'd maybe some further annotations to suggest something like io::Result<()>, but that would require some form of annotation on io::Error that links to the io::Result alias, I imagine. It seems like starting with suggesting that the function return a Result would be so much better than we have.

So I don't think "consider changing the return type to a Result" is an appropriate suggestion for main, since the return type of main MUST be (), and we'll have just suggested they make a change that leads to another error. Similarly with methods of a trait that someone might be implementing. If we can't distinguish those two situations from cases when the person totally controls the function signature, then I don't think we should have this suggestion at all :-/

I do like what you have in the text for the case when someone tries to use ? on something that doesn't implement it ("? cannot be applied to a value of type Foo"). Should we list types that DO implement Try in scope? Or should we suggest implementing Try on Foo? That might not always be a good suggestion...

@nikomatsakis

This comment has been minimized.

Copy link
Contributor Author

commented Jan 20, 2017

@carols10cents

So I don't think "consider changing the return type to a Result" is an appropriate suggestion for main, since the return type of main MUST be ()

I agree, although my preference would be to implement the solutions proposed in this thread on internals, such that you could change the return type on main

If we can't distinguish those two situations from cases when the person totally controls the function signature, then I don't think we should have this suggestion at all :-/

In any case, good point, I see no reason we cannot distinguish the context. In the case of a trait, one could also imagine that, in the case that the trait is thread-local, suggesting that one might consider modifying the trait itself.

Should we list types that DO implement Try in scope? Or should we suggest implementing Try on Foo? That might not always be a good suggestion...

To me it seems like overkill. In the first case, it doesn't seem worth mentioning types that do implement try, since the value in question is not of those types. In the second, it's not clear that implementing try is a good idea (or even possible, given the coherence rules).

@nikomatsakis

This comment has been minimized.

Copy link
Contributor Author

commented Jan 20, 2017

@carols10cents updated to address your latest suggestions

@nikomatsakis

This comment has been minimized.

Copy link
Contributor Author

commented Jan 20, 2017

@carols10cents

Consider extracting the code where you want to use the ? into a function that returns Result and using a match statement here to handle the return value from the new function

Ah, I overlooked this. I don't quite understand what pattern you are suggesting, can you maybe given an example?

@SimonSapin

This comment has been minimized.

Copy link
Contributor

commented Jan 21, 2017

When catch blocks are implemented, the error message could advise to introduce one. This would also work for main().

@carols10cents

This comment has been minimized.

Copy link
Member

commented Jan 21, 2017

@carols10cents

Consider extracting the code where you want to use the ? into a function that returns Result and using a match statement here to handle the return value from the new function

Ah, I overlooked this. I don't quite understand what pattern you are suggesting, can you maybe given an example?

Oh, I just mean like in this comment, where your main function is just:

fn main() {
    match do_all_the_things() {
       Ok(_) => //whatever
        Err(e) => // handle any errors nicely
    } 
}

and then do_all_the_things and all the functions it calls can return Result and use ?.

@carols10cents

This comment has been minimized.

Copy link
Member

commented Jan 21, 2017

@nikomatsakis

This comment has been minimized.

Copy link
Contributor Author

commented Jan 21, 2017

@SimonSapin

When catch blocks are implemented, the error message could advise to introduce one. This would also work for main().

Agreed.

This comment is actually a better example.

I see. It seems to me that this is a lot to fit into the immediate error message, but it might be a good candidate for the extended error message.

@carols10cents

This comment has been minimized.

Copy link
Member

commented on text/0000-try-trait.md in 9af60a1 Jan 21, 2017

nit: I think line 300 is meant to say "then we should NOT make this suggestion"?

@carols10cents

This comment has been minimized.

Copy link
Member

commented Jan 21, 2017

<3 love the latest changes in f89568b, this looks great now :)

@mitsuhiko

This comment has been minimized.

Copy link
Contributor

commented May 4, 2017

Quick remark: for readers from the future that will discover this RFC through the feature it might be interesting to add a note on why it's Missing and not (). Maybe just a link to @aturon's comment here (#1859 (comment)) is sufficient.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor Author

commented May 4, 2017

@mitsuhiko

Quick remark: for readers from the future that will discover this RFC through the feature it might be interesting to add a note on why it's Missing and not (). Maybe just a link to @aturon's comment here (#1859 (comment)) is sufficient.

This is I think what this text was meant to convey:

The use of a fresh type like Missing is recommended whenever one
implements Try for a type that does not have the #[must_use]
attribute (or, more semantically, that does not represent an
"unhandled error").

but I guess I should make the connection to () more explicit.

@stbuehler

This comment has been minimized.

Copy link

commented May 8, 2017

Thx @bluss for pointing me here. I asked in rust-lang/rust#31436 why the conversion was implemented using From instead of Into. As it turns out changing this now would be rather difficult (breaking existing code).

I thought I needed Into to have a generic error type in a possibly external crate, and make local errors convertible to this generic error. I found a solution (which is probably obvious to most experts in here): have a custom IntoCustomError trait, and implement From based on that. Maybe this could be used as an example in the book or somewhere else: https://play.rust-lang.org/?gist=41a425fc633ddd81b90a92e3479c9886

@nrc

This comment has been minimized.

Copy link
Member

commented May 17, 2017

ping @pnkfelix needs signoff - #1859 (comment) (I want my glorious Option/?!)

@pnkfelix

This comment has been minimized.

Copy link
Member

commented May 17, 2017

@rfcbot reviewed

@rfcbot

This comment has been minimized.

Copy link

commented May 17, 2017

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot

This comment has been minimized.

Copy link

commented May 27, 2017

The final comment period is now complete.

@repax
Copy link

left a comment

(typo)


```rust
fn foo() -> Poll<T, E> {
let x = bar()?; // propogate error case

This comment has been minimized.

Copy link
@repax

repax May 27, 2017

"propagate"


```rust
fn foo() -> Result<T, E> {
let x = bar()?; // propogate error case

This comment has been minimized.

Copy link
@repax

repax May 27, 2017

"propagate"

@nikomatsakis nikomatsakis merged commit 80db2d9 into rust-lang:master May 30, 2017

@nikomatsakis

This comment has been minimized.

Copy link
Contributor Author

commented May 30, 2017

Thanks all. I've merged the RFC and re-used the existing tracking issue.

frewsxcv added a commit to frewsxcv/rust that referenced this pull request Jun 1, 2017

Rollup merge of rust-lang#42275 - scottmcm:try-trait, r=nikomatsakis
Lower `?` to `Try` instead of `Carrier`

The easy parts of rust-lang/rfcs#1859, whose FCP completed without further comments.

Just the trait and the lowering -- neither the error message improvements nor the insta-stable impl for Option nor exhaustive docs.

Based on a [github search](https://github.com/search?l=rust&p=1&q=question_mark_carrier&type=Code&utf8=%E2%9C%93), this will break the following:

- https://github.com/pfpacket/rust-9p/blob/00206e34c680198a0ac7c2f066cc2954187d4fac/src/serialize.rs#L38
- https://github.com/peterdelevoryas/bufparse/blob/b1325898f4fc2c67658049196c12da82548af350/src/result.rs#L50

The other results appear to be files from libcore or its tests.  I could also leave Carrier around after stage0 and `impl<T:Carrier> Try for T` if that would be better.

r? @nikomatsakis

Edit: Oh, and it might accidentally improve perf, based on rust-lang#37939 (comment), since `Try::into_result` for `Result` is an obvious no-op, unlike `Carrier::translate`.

frewsxcv added a commit to frewsxcv/rust that referenced this pull request Jun 1, 2017

Rollup merge of rust-lang#42275 - scottmcm:try-trait, r=nikomatsakis
Lower `?` to `Try` instead of `Carrier`

The easy parts of rust-lang/rfcs#1859, whose FCP completed without further comments.

Just the trait and the lowering -- neither the error message improvements nor the insta-stable impl for Option nor exhaustive docs.

Based on a [github search](https://github.com/search?l=rust&p=1&q=question_mark_carrier&type=Code&utf8=%E2%9C%93), this will break the following:

- https://github.com/pfpacket/rust-9p/blob/00206e34c680198a0ac7c2f066cc2954187d4fac/src/serialize.rs#L38
- https://github.com/peterdelevoryas/bufparse/blob/b1325898f4fc2c67658049196c12da82548af350/src/result.rs#L50

The other results appear to be files from libcore or its tests.  I could also leave Carrier around after stage0 and `impl<T:Carrier> Try for T` if that would be better.

r? @nikomatsakis

Edit: Oh, and it might accidentally improve perf, based on rust-lang#37939 (comment), since `Try::into_result` for `Result` is an obvious no-op, unlike `Carrier::translate`.

frewsxcv added a commit to frewsxcv/rust that referenced this pull request Jun 1, 2017

Rollup merge of rust-lang#42275 - scottmcm:try-trait, r=nikomatsakis
Lower `?` to `Try` instead of `Carrier`

The easy parts of rust-lang/rfcs#1859, whose FCP completed without further comments.

Just the trait and the lowering -- neither the error message improvements nor the insta-stable impl for Option nor exhaustive docs.

Based on a [github search](https://github.com/search?l=rust&p=1&q=question_mark_carrier&type=Code&utf8=%E2%9C%93), this will break the following:

- https://github.com/pfpacket/rust-9p/blob/00206e34c680198a0ac7c2f066cc2954187d4fac/src/serialize.rs#L38
- https://github.com/peterdelevoryas/bufparse/blob/b1325898f4fc2c67658049196c12da82548af350/src/result.rs#L50

The other results appear to be files from libcore or its tests.  I could also leave Carrier around after stage0 and `impl<T:Carrier> Try for T` if that would be better.

r? @nikomatsakis

Edit: Oh, and it might accidentally improve perf, based on rust-lang#37939 (comment), since `Try::into_result` for `Result` is an obvious no-op, unlike `Carrier::translate`.

@pnkfelix pnkfelix referenced this pull request Jun 2, 2017

Closed

Traits for `?`? #2019

@kennytm kennytm referenced this pull request Jun 14, 2017

Merged

Impl Try for Option #42526

@stjepang stjepang referenced this pull request Nov 11, 2017

Merged

Introduce crossbeam-deque #21

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.