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

@nikomatsakis nikomatsakis 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 Relevant to the language team, which will review and decide on the RFC. label Jan 19, 2017
@nikomatsakis
Copy link
Contributor Author

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

@withoutboats
Copy link
Contributor

@@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you want to fill these in? ^^

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a typo.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

@shepmaster
Copy link
Member

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
Copy link
Member

shepmaster 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
Copy link
Member

carols10cents 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?

@clarfonthey
Copy link
Contributor

clarfonthey 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
Copy link
Contributor Author

nikomatsakis 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
Copy link
Contributor Author

@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
Copy link
Contributor Author

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...).

@clarfonthey
Copy link
Contributor

@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
Copy link
Member

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
Copy link
Contributor Author

@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
Copy link
Contributor Author

@carols10cents updated to address your latest suggestions

@nikomatsakis
Copy link
Contributor Author

@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
Copy link
Contributor

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

@carols10cents
Copy link
Member

@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
Copy link
Member

@nikomatsakis
Copy link
Contributor Author

@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
Copy link
Member

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

@stbuehler
Copy link

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

@aturon aturon removed the final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. label May 11, 2017
@nrc
Copy link
Member

nrc commented May 17, 2017

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

@pnkfelix
Copy link
Member

@rfcbot reviewed

@rfcbot
Copy link
Collaborator

rfcbot commented May 17, 2017

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

@rfcbot rfcbot added the final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. label May 17, 2017
@rfcbot
Copy link
Collaborator

rfcbot commented May 27, 2017

The final comment period is now complete.

Copy link

@repax repax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(typo)


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"propagate"


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"propagate"

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

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
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
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 mentioned this pull request Jun 2, 2017
@Centril Centril added A-impls-libstd Standard library implementations related proposals. A-error-handling Proposals relating to error handling. labels Nov 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-error-handling Proposals relating to error handling. A-impls-libstd Standard library implementations related proposals. final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.