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
Contributor

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 label Jan 19, 2017

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jan 19, 2017

Contributor

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

Contributor

nikomatsakis commented Jan 19, 2017

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

Show outdated Hide outdated text/0000-try-trait.md
@@ -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.

@shepmaster

shepmaster Jan 19, 2017

Member

Did you want to fill these in? ^^

@shepmaster

shepmaster Jan 19, 2017

Member

Did you want to fill these in? ^^

This comment has been minimized.

@nikomatsakis

nikomatsakis Jan 20, 2017

Contributor

done.

@nikomatsakis

nikomatsakis Jan 20, 2017

Contributor

done.

Show outdated Hide outdated text/0000-try-trait.md
### 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.

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

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

@nikomatsakis

nikomatsakis Jan 20, 2017

Contributor

That is a typo.

@nikomatsakis

nikomatsakis Jan 20, 2017

Contributor

That is a typo.

This comment has been minimized.

@nikomatsakis

nikomatsakis Jan 20, 2017

Contributor

fixed.

@nikomatsakis

nikomatsakis Jan 20, 2017

Contributor

fixed.

@shepmaster

This comment has been minimized.

Show comment
Hide comment
@shepmaster

shepmaster Jan 19, 2017

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.

Member

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

Show comment
Hide comment
@shepmaster

shepmaster Jan 19, 2017

Member

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.

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

This comment has been minimized.

Show comment
Hide comment
@carols10cents

carols10cents Jan 19, 2017

Member

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?

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?

@clarcharr

This comment has been minimized.

Show comment
Hide comment
@clarcharr

clarcharr Jan 20, 2017

Contributor

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.

Contributor

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

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jan 20, 2017

Contributor

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

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jan 20, 2017

Contributor

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

Contributor

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

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jan 20, 2017

Contributor

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

Contributor

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

@clarcharr

This comment has been minimized.

Show comment
Hide comment
@clarcharr

clarcharr Jan 20, 2017

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?

Contributor

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

Show comment
Hide comment
@carols10cents

carols10cents Jan 20, 2017

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

Member

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

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jan 20, 2017

Contributor

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

Contributor

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

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jan 20, 2017

Contributor

@carols10cents updated to address your latest suggestions

Contributor

nikomatsakis commented Jan 20, 2017

@carols10cents updated to address your latest suggestions

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jan 20, 2017

Contributor

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

Contributor

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

Show comment
Hide comment
@SimonSapin

SimonSapin Jan 21, 2017

Contributor

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

Contributor

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

Show comment
Hide comment
@carols10cents

carols10cents Jan 21, 2017

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

Member

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

Show comment
Hide comment
@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jan 21, 2017

Contributor

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

Contributor

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

Show comment
Hide comment
@carols10cents

carols10cents Jan 21, 2017

Member

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

Member

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

Show comment
Hide comment
@carols10cents

carols10cents Jan 21, 2017

Member

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

Member

carols10cents commented Jan 21, 2017

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

@carols10cents carols10cents referenced this pull request Jan 23, 2017

Closed

Iterators #395

Show outdated Hide outdated text/0000-try-trait.md
determined by the type of `x`.
In fact, if we wanted to ensure that the type of `x?` is determined
*solely* by `x` and not by the surrouding return type `E`, we might

This comment has been minimized.

@soltanmm

soltanmm Jan 23, 2017

nit: s/surrouding/surrounding

@soltanmm

soltanmm Jan 23, 2017

nit: s/surrouding/surrounding

Show outdated Hide outdated text/0000-try-trait.md
are able to infer the type of `collect()` from context. This may
require small tweaks to the `Try` trait, though the author considers
that unlikely.

This comment has been minimized.

@bluss

bluss Jan 23, 2017

There is another and more serious type inference issue: Whenever you have nested try!() or nested ?, the two error conversions don't infer when together. The usual case is that you don't want any error conversion at all at one of the two levels. From<X>: Y would somehow gently fall back to X == Y to have this case compile.

Compared with that, I think the issue where try!(x.collect()) infers the collect to use a Result is merely a cute trick.

@bluss

bluss Jan 23, 2017

There is another and more serious type inference issue: Whenever you have nested try!() or nested ?, the two error conversions don't infer when together. The usual case is that you don't want any error conversion at all at one of the two levels. From<X>: Y would somehow gently fall back to X == Y to have this case compile.

Compared with that, I think the issue where try!(x.collect()) infers the collect to use a Result is merely a cute trick.

This comment has been minimized.

@nikomatsakis

nikomatsakis Jan 23, 2017

Contributor

Hmm, I agree this looks a bit frustrating, though it seems like an orthogonal concern. That is, it is equally applicable today, right? That is, if I understand the problem you are referring to, it would occur with try! as well, right? (Or am I confused?)

@nikomatsakis

nikomatsakis Jan 23, 2017

Contributor

Hmm, I agree this looks a bit frustrating, though it seems like an orthogonal concern. That is, it is equally applicable today, right? That is, if I understand the problem you are referring to, it would occur with try! as well, right? (Or am I confused?)

This comment has been minimized.

@shepmaster

shepmaster Jan 23, 2017

Member

if I understand the problem you are referring to, it would occur with try! as well, right?

I think so:

Whenever you have nested try!()

@shepmaster

shepmaster Jan 23, 2017

Member

if I understand the problem you are referring to, it would occur with try! as well, right?

I think so:

Whenever you have nested try!()

This comment has been minimized.

@bluss

bluss Jan 23, 2017

Yes you are right. It would be diligent to preserve the “cute” collect trick when migrating from try to ? but it's not a pressing issue like error conversion. Nesting ? doesn't work without type annotations on closures or what it may be; that is not good for the intended design of ? and catch.

@bluss

bluss Jan 23, 2017

Yes you are right. It would be diligent to preserve the “cute” collect trick when migrating from try to ? but it's not a pressing issue like error conversion. Nesting ? doesn't work without type annotations on closures or what it may be; that is not good for the intended design of ? and catch.

This comment has been minimized.

@nikomatsakis

nikomatsakis Jan 24, 2017

Contributor

It would be diligent to preserve the “cute” collect trick when migrating from try to ? but it's not a pressing issue like error conversion.

I'm not sure what you mean. When I write code using ? today, I still have to use try! whenever there is a call to collect(). This happens to me a lot. Seems like a problem.

Nesting ? doesn't work without type annotations on closures or what it may be; that is not good for the intended design of ? and catch.

But this is also true for try!, right? It's not clear to me why this is a bigger problem for ? and catch than try!. Closures in particular tend to get their return types inferred from context. In any case, I agree it's a problem we should try to address, though the best solution is unclear; it seems to require some sort of inference tricks.

In any case, it's not like we can remove error conversion! It's already stabilized, and we want to be able to serve as a drop-in replacement for try!.

@nikomatsakis

nikomatsakis Jan 24, 2017

Contributor

It would be diligent to preserve the “cute” collect trick when migrating from try to ? but it's not a pressing issue like error conversion.

I'm not sure what you mean. When I write code using ? today, I still have to use try! whenever there is a call to collect(). This happens to me a lot. Seems like a problem.

Nesting ? doesn't work without type annotations on closures or what it may be; that is not good for the intended design of ? and catch.

But this is also true for try!, right? It's not clear to me why this is a bigger problem for ? and catch than try!. Closures in particular tend to get their return types inferred from context. In any case, I agree it's a problem we should try to address, though the best solution is unclear; it seems to require some sort of inference tricks.

In any case, it's not like we can remove error conversion! It's already stabilized, and we want to be able to serve as a drop-in replacement for try!.

This comment has been minimized.

@bluss

bluss Jan 24, 2017

I think that try!() forces collect to pick the Result impl is merely a cute trick — the usual way to use .collect() is to specify which type you are collecting to, so specifying that inside .collect::<Result<Vec<_>, _>>()? seems frankly normal.

I am just pointing out that there is a problem with type inference and error conversion, and I am not proposing that it should be removed. But it would be a lot more useful if no-ops (no conversion) was a fallback mode.

@bluss

bluss Jan 24, 2017

I think that try!() forces collect to pick the Result impl is merely a cute trick — the usual way to use .collect() is to specify which type you are collecting to, so specifying that inside .collect::<Result<Vec<_>, _>>()? seems frankly normal.

I am just pointing out that there is a problem with type inference and error conversion, and I am not proposing that it should be removed. But it would be a lot more useful if no-ops (no conversion) was a fallback mode.

This comment has been minimized.

@nikomatsakis

nikomatsakis Jan 24, 2017

Contributor

@bluss ok, I understand. I agree it'd be nice if no conversion was a fallback, but that seems to be an orthogonal issue, and I'm not sure the best way to achieve it. (Fallback in inference is kind of a complicated topic, though I have some thoughts about how we might do it in a more principled way in the future.)

@nikomatsakis

nikomatsakis Jan 24, 2017

Contributor

@bluss ok, I understand. I agree it'd be nice if no conversion was a fallback, but that seems to be an orthogonal issue, and I'm not sure the best way to achieve it. (Fallback in inference is kind of a complicated topic, though I have some thoughts about how we might do it in a more principled way in the future.)

@F001

This comment has been minimized.

Show comment
Hide comment
@F001

F001 Jan 25, 2017

Contributor

Could we add an additional macro, which wraps statements into a closure? Just like this:

fn main() {  // return type remains unit type
  let result = block! {
        ...
       //  codes using ? oprator
  };
  match result {
      Ok(_) =>{}
      Err(_) =>{}
  }
}

While the macro block! just wraps all the statements into a closure, and call it immediately.

Contributor

F001 commented Jan 25, 2017

Could we add an additional macro, which wraps statements into a closure? Just like this:

fn main() {  // return type remains unit type
  let result = block! {
        ...
       //  codes using ? oprator
  };
  match result {
      Ok(_) =>{}
      Err(_) =>{}
  }
}

While the macro block! just wraps all the statements into a closure, and call it immediately.

@CryZe

This comment has been minimized.

Show comment
Hide comment
@CryZe

CryZe Jan 25, 2017

@F001 That's what the unimplemented catch { ... } block is for.

CryZe commented Jan 25, 2017

@F001 That's what the unimplemented catch { ... } block is for.

@yupferris

This comment has been minimized.

Show comment
Hide comment
@yupferris

yupferris Jan 26, 2017

I'm seeing a lot of references to this unimplemented catch block, but this is news to me. Where can I find info about this?

I'm seeing a lot of references to this unimplemented catch block, but this is news to me. Where can I find info about this?

@nikomatsakis nikomatsakis self-assigned this Jan 26, 2017

@aturon aturon referenced this pull request Feb 1, 2017

Open

Language ergonomic/learnability improvements #17

10 of 31 tasks complete
@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Feb 11, 2017

Contributor

@rfcbot fcp merge

Given the lack of controversy here, I'm going to go ahead and propose that we merge this proposal. I don't have much to add beyond the existing text of the RFC: the conversation primarily focused around questions of error messages (which I have incorporated into the RFC) as well as some discussion around the painful interaction between Err::from and nested ?, which seems orthogonal to this RFC in particular.

Contributor

nikomatsakis commented Feb 11, 2017

@rfcbot fcp merge

Given the lack of controversy here, I'm going to go ahead and propose that we merge this proposal. I don't have much to add beyond the existing text of the RFC: the conversation primarily focused around questions of error messages (which I have incorporated into the RFC) as well as some discussion around the painful interaction between Err::from and nested ?, which seems orthogonal to this RFC in particular.

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Apr 21, 2017

Contributor

OK, so, I tried a number of variations around futures. I was able to get a setup that works for all of @alexcrichton's examples:

fn bar() -> Poll<i32, u32> {
        let a: i32 = question_mark!(result());
        let b: Async<i32> = question_mark!(poll().err());
        let c: i32 = question_mark!(poll());
        let d: Result<i32, u32> = question_mark!(poll().not_ready());

        
        poll()
}

The idea is that applying ? to a Poll<T,E> value yields a T by default, and propagates either not-ready or error. You can use the err() and not_ready() adapters to only propagate errors and only propagate "not ready", respectively.

For this to work, we basically convert a Poll<T,E> into a Result<T, Async<E>> in the Try trait. The err() and not_ready() convert to a Result<Async<T>, E> and Result<Result<T,E>, Async<E>>, respectively.

There is one catch, and it's sort of interesting: you don't get error conversion in this system. This is because there is a From impl that lets you go from E to Async<E>, but we can't write one that lets you go from F to Async<E> where E: From<F> (nor, I think, Async<F> to Async<E>). I think we could overcome these limitations with some amount of negative reasoning and/or maybe specialization.

(I find this interesting because an advantage of the "reductionist" approach was the universal application of From -- but in this case, the problems, I think, arise because Poll actually has 3 states, and "reducing" those 3 states into 2 makes a kind of mismatch, so that the From impl is actually playing a different role than the one it was meant to play.)

(Still, the essentialist approach also ran into some limitations here, e.g. the orphan rules prevented us from going from Result -> Poll, which works in this setup. I feel more confident that we can lift the overlap rules I mentioned in any case.)

Contributor

nikomatsakis commented Apr 21, 2017

OK, so, I tried a number of variations around futures. I was able to get a setup that works for all of @alexcrichton's examples:

fn bar() -> Poll<i32, u32> {
        let a: i32 = question_mark!(result());
        let b: Async<i32> = question_mark!(poll().err());
        let c: i32 = question_mark!(poll());
        let d: Result<i32, u32> = question_mark!(poll().not_ready());

        
        poll()
}

The idea is that applying ? to a Poll<T,E> value yields a T by default, and propagates either not-ready or error. You can use the err() and not_ready() adapters to only propagate errors and only propagate "not ready", respectively.

For this to work, we basically convert a Poll<T,E> into a Result<T, Async<E>> in the Try trait. The err() and not_ready() convert to a Result<Async<T>, E> and Result<Result<T,E>, Async<E>>, respectively.

There is one catch, and it's sort of interesting: you don't get error conversion in this system. This is because there is a From impl that lets you go from E to Async<E>, but we can't write one that lets you go from F to Async<E> where E: From<F> (nor, I think, Async<F> to Async<E>). I think we could overcome these limitations with some amount of negative reasoning and/or maybe specialization.

(I find this interesting because an advantage of the "reductionist" approach was the universal application of From -- but in this case, the problems, I think, arise because Poll actually has 3 states, and "reducing" those 3 states into 2 makes a kind of mismatch, so that the From impl is actually playing a different role than the one it was meant to play.)

(Still, the essentialist approach also ran into some limitations here, e.g. the orphan rules prevented us from going from Result -> Poll, which works in this setup. I feel more confident that we can lift the overlap rules I mentioned in any case.)

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Apr 21, 2017

Contributor

@alexcrichton

Is there a use case for constructing option::Missing? If not we could make it have a private field to prevent construction externally. (prevent easy construction at least)

I had imagined that one might want to write Err(Missing) explicitly.

To clarify, we don't expect Try to be in the prelude, right?

No. I debated about making it's members be not methods but just functions, since I would never expect them to be called with dot-notation. But it seemed to make it less natural to implement so I left that out.

The only possible change I could think of to make it more amenable (but not even sure if we'd need such a change) would be to change it to Try<T, E> instead of associated types, but that sounds like one of those things which has been debated to death and is probably good to have associated types.

So @alexcrichton and I talked a lot about this on IRC. I think that using input types introduces a lot of variables -- both metaphorically, and literally, in terms of type variables. For one thing, it implies that the meaning (and type) of x? is no longer independent of context, unless we go back to having more than one trait. Then, to make type inference work out, you probably can't have the From impl be independent of FromError (because otherwise you wind up with something like <ReturnType as FromError<?T>>::from_error(?T::from(err)), where ?T is unconstrained. To fix this, you want to bake From into FromError, and you are quickly arriving back at the essentialist trait, from what I can tell. I think you really probably do want to keep the types as associated types.

Contributor

nikomatsakis commented Apr 21, 2017

@alexcrichton

Is there a use case for constructing option::Missing? If not we could make it have a private field to prevent construction externally. (prevent easy construction at least)

I had imagined that one might want to write Err(Missing) explicitly.

To clarify, we don't expect Try to be in the prelude, right?

No. I debated about making it's members be not methods but just functions, since I would never expect them to be called with dot-notation. But it seemed to make it less natural to implement so I left that out.

The only possible change I could think of to make it more amenable (but not even sure if we'd need such a change) would be to change it to Try<T, E> instead of associated types, but that sounds like one of those things which has been debated to death and is probably good to have associated types.

So @alexcrichton and I talked a lot about this on IRC. I think that using input types introduces a lot of variables -- both metaphorically, and literally, in terms of type variables. For one thing, it implies that the meaning (and type) of x? is no longer independent of context, unless we go back to having more than one trait. Then, to make type inference work out, you probably can't have the From impl be independent of FromError (because otherwise you wind up with something like <ReturnType as FromError<?T>>::from_error(?T::from(err)), where ?T is unconstrained. To fix this, you want to bake From into FromError, and you are quickly arriving back at the essentialist trait, from what I can tell. I think you really probably do want to keep the types as associated types.

@clarcharr

This comment has been minimized.

Show comment
Hide comment
@clarcharr

clarcharr Apr 22, 2017

Contributor

Here's an idea I came up with that could solve the inherent-error problem: why not wrapper types?

For example, we could add try_some and try_none methods to Option that provide wrapper types TrySome and TryNone. Then we implement Try for those types instead of option, with TrySome unwrapping the Some or returning None, and TryNone yielding () on None or returning Some.

We could also add a TryOk, although a TryErr wouldn't make sense because Result and TryErr are the same.

let val = iter.next().try_some()? would be a pretty nice way of dealing with this IMHO. It'd also allow us to limit the implementations to just Result and wrapper types. And it looks decently idiomatic to me.

Contributor

clarcharr commented Apr 22, 2017

Here's an idea I came up with that could solve the inherent-error problem: why not wrapper types?

For example, we could add try_some and try_none methods to Option that provide wrapper types TrySome and TryNone. Then we implement Try for those types instead of option, with TrySome unwrapping the Some or returning None, and TryNone yielding () on None or returning Some.

We could also add a TryOk, although a TryErr wouldn't make sense because Result and TryErr are the same.

let val = iter.next().try_some()? would be a pretty nice way of dealing with this IMHO. It'd also allow us to limit the implementations to just Result and wrapper types. And it looks decently idiomatic to me.

@colin-kiegel

This comment has been minimized.

Show comment
Hide comment
@colin-kiegel

colin-kiegel Apr 22, 2017

@alexcrichton I'm not entirely sure what you meant by TryInto though? Could you elaborate on the desugaring of ? with the TryInto trait?

I have to admit, that it was just a loose idea. ^^' But I have tried to make it work and came up with this. As anticipated it allows all your uses cases (without prior type conversions).

In my example the Try<T> trait just extends TryInto<T>. Of course it would be possible to define Try<T> independently.

pub trait Try<T>: TryInto<T> {
    fn from_error(Self::Error) -> Self;
}

////// from std //////
pub trait TryInto<T> {
    type Error;
    fn try_into(self) -> Result<T, Self::Error>;
}

Now the part you asked for: desugaring of x? — (..I was forced to define a helper function):

macro_rules! question_mark {
    ($x:expr) => {
        match try_into($x) {
            Ok(v) => v,
            Err(e) => return e,
        }
    }
}

/// This helper function connects the incoming type `I: Try<T>` and the
/// outgoing type `E: <Try>` via `E::Error: From<I::Error>`, because that
/// cannot be inferred when inlined.
pub fn try_into<T, I: Try<T>, E: Try<T>>(x: I) -> Result<T, E>
    where E::Error: From<I::Error>,
{
    match TryInto::try_into(x) {
        Ok(v) => Ok(v),
        Err(e) => Err(Try::from_error(From::from(e))),
    }
}

Any attempt to get rid of that helper function seems to fail due to inference

macro_rules! question_mark {
    ($x:expr) => {
        match TryInto::try_into($x) {
            Ok(v) => v,
            Err(e) => return Try::from_error(From::from(e)),
            //               ^^^^^^^^^^^^^^^
            // type annotations required: cannot resolve
            // `futures::Poll<i32, u32>: Try<_>`
        }
    }
}

// ..
mod futures {
    fn bar() -> Poll<i32, u32> {
        let a: i32 = question_mark!(result());
        //           ------------------------ in this macro invocation
    }
}

Discussion

Would that be any good? Hm...

I think it's one thing to have potentially expensive conversions on the error-path. But with this TryInto behaviour every use of ? could mask an expensive operation on the happy path. I think it's better to have these conversions be explicit like @nikomatsakis suggested, but maybe with names which focus on the happy path poll.ok()?, poll.async()?, poll.result()?. Or more generically poll.try_into()?

colin-kiegel commented Apr 22, 2017

@alexcrichton I'm not entirely sure what you meant by TryInto though? Could you elaborate on the desugaring of ? with the TryInto trait?

I have to admit, that it was just a loose idea. ^^' But I have tried to make it work and came up with this. As anticipated it allows all your uses cases (without prior type conversions).

In my example the Try<T> trait just extends TryInto<T>. Of course it would be possible to define Try<T> independently.

pub trait Try<T>: TryInto<T> {
    fn from_error(Self::Error) -> Self;
}

////// from std //////
pub trait TryInto<T> {
    type Error;
    fn try_into(self) -> Result<T, Self::Error>;
}

Now the part you asked for: desugaring of x? — (..I was forced to define a helper function):

macro_rules! question_mark {
    ($x:expr) => {
        match try_into($x) {
            Ok(v) => v,
            Err(e) => return e,
        }
    }
}

/// This helper function connects the incoming type `I: Try<T>` and the
/// outgoing type `E: <Try>` via `E::Error: From<I::Error>`, because that
/// cannot be inferred when inlined.
pub fn try_into<T, I: Try<T>, E: Try<T>>(x: I) -> Result<T, E>
    where E::Error: From<I::Error>,
{
    match TryInto::try_into(x) {
        Ok(v) => Ok(v),
        Err(e) => Err(Try::from_error(From::from(e))),
    }
}

Any attempt to get rid of that helper function seems to fail due to inference

macro_rules! question_mark {
    ($x:expr) => {
        match TryInto::try_into($x) {
            Ok(v) => v,
            Err(e) => return Try::from_error(From::from(e)),
            //               ^^^^^^^^^^^^^^^
            // type annotations required: cannot resolve
            // `futures::Poll<i32, u32>: Try<_>`
        }
    }
}

// ..
mod futures {
    fn bar() -> Poll<i32, u32> {
        let a: i32 = question_mark!(result());
        //           ------------------------ in this macro invocation
    }
}

Discussion

Would that be any good? Hm...

I think it's one thing to have potentially expensive conversions on the error-path. But with this TryInto behaviour every use of ? could mask an expensive operation on the happy path. I think it's better to have these conversions be explicit like @nikomatsakis suggested, but maybe with names which focus on the happy path poll.ok()?, poll.async()?, poll.result()?. Or more generically poll.try_into()?

@scottmcm

This comment has been minimized.

Show comment
Hide comment
@scottmcm

scottmcm Apr 23, 2017

Member

Great work on the RFC updates, @nikomatsakis! It flows well, and I particularly like the middle-ground description of "be sure that this sort of interconversion is intentional".

variations around futures

Good to see this written up. I like the approach of having a short method when you want something different from the default. (I suppose they could even return impl Try<Ok=...,Error=...> to discourage using anything but ? on the returned value.) Bikeshed: I think I agree with @colin-kiegel that I'd have expected the opposite names, so it'd be poll().ready()? : Result<T,E>, where the .ready()? is behaving sortof like an if .is_ready().

I'm glad you arrived at Poll<T,E>::Ok = T. Looking at success-wrapping, I think that's the only permutation that avoids a "Poll::Ok(()) problem" in something like an async write_all.

Losing error conversion there definitely makes me sad, as it probably means futures wouldn't move over to Try until it could impl<E, F: Into<E>> From<Async<F>> for Async<E>. I guess that needs intersection impls or a where E != F? (Hmm, Option probably wants the same thing.)


Is there a use case for constructing option::Missing?

I don't think there is today. I'm not sure why I'd ever want to write Ok(x)/Err(Missing) instead of Some(x)/None, so preventing pub construction for now sounds good (and it can easily be allowed later).

If we get a throw sugar, though, it would be useful. Demonstration (not clearly better than Some/None here, but as an example):

fn checked_abs(x: i32) -> Option<i32> throws {
    if x == i32::MIN { throw Missing; }
    if x < 0 { -x }
    else { x }
}

Looking at that, though, Missing is a rather weird name for what's happening. It feels like it would be somewhat more natural for it to be throw None.

Interestingly, we do have a ZST that has None as its value-namespace constructor: Option<!>. That means we could do this:

type None = Option<!>;
impl<T> Try for Option<T>  {
    type Ok = T;
    type Error = None;
    fn into_result(self) -> Result<T, None> { self.ok_or(None) }
    fn from_ok(v: T) -> Self { Some(v) }
    fn from_error(_: None) -> Self { None }
}

I can't make up my mind whether that's the good kind of clever or the abusive kind of clever, though...

Member

scottmcm commented Apr 23, 2017

Great work on the RFC updates, @nikomatsakis! It flows well, and I particularly like the middle-ground description of "be sure that this sort of interconversion is intentional".

variations around futures

Good to see this written up. I like the approach of having a short method when you want something different from the default. (I suppose they could even return impl Try<Ok=...,Error=...> to discourage using anything but ? on the returned value.) Bikeshed: I think I agree with @colin-kiegel that I'd have expected the opposite names, so it'd be poll().ready()? : Result<T,E>, where the .ready()? is behaving sortof like an if .is_ready().

I'm glad you arrived at Poll<T,E>::Ok = T. Looking at success-wrapping, I think that's the only permutation that avoids a "Poll::Ok(()) problem" in something like an async write_all.

Losing error conversion there definitely makes me sad, as it probably means futures wouldn't move over to Try until it could impl<E, F: Into<E>> From<Async<F>> for Async<E>. I guess that needs intersection impls or a where E != F? (Hmm, Option probably wants the same thing.)


Is there a use case for constructing option::Missing?

I don't think there is today. I'm not sure why I'd ever want to write Ok(x)/Err(Missing) instead of Some(x)/None, so preventing pub construction for now sounds good (and it can easily be allowed later).

If we get a throw sugar, though, it would be useful. Demonstration (not clearly better than Some/None here, but as an example):

fn checked_abs(x: i32) -> Option<i32> throws {
    if x == i32::MIN { throw Missing; }
    if x < 0 { -x }
    else { x }
}

Looking at that, though, Missing is a rather weird name for what's happening. It feels like it would be somewhat more natural for it to be throw None.

Interestingly, we do have a ZST that has None as its value-namespace constructor: Option<!>. That means we could do this:

type None = Option<!>;
impl<T> Try for Option<T>  {
    type Ok = T;
    type Error = None;
    fn into_result(self) -> Result<T, None> { self.ok_or(None) }
    fn from_ok(v: T) -> Self { Some(v) }
    fn from_error(_: None) -> Self { None }
}

I can't make up my mind whether that's the good kind of clever or the abusive kind of clever, though...

@alexcrichton

This comment has been minimized.

Show comment
Hide comment
@alexcrichton

alexcrichton Apr 24, 2017

Member

Whoa nice investigation @colin-kiegel! Unfortunately though I think that once you start splitting it up at the crate boundary it runs afoul of coherence :(

error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g. `MyStruct<T>`); only traits defined in the current crate can be implemented for a type parameter
  --> bar.rs:60:5
   |
60 |     impl<T, E> TryFrom<Poll<T, E>> for T {
   |     ^

error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g. `MyStruct<T>`); only traits defined in the current crate can be implemented for a type parameter
   --> bar.rs:103:5
    |
103 |     impl<T, E> TryFrom<Poll<T, E>> for Result<T, E> {
    |     ^

error: aborting due to 2 previous errors

I wonder, though, if using short methods (like poll.ready()?) would still allow this to be possible? For example maybe:

fn foo() -> Poll<T, E> {
    let a: T = (e: Result<T, E>)?; // propagate Result error
    let b: Async<T> = (e: Poll<T, E>).maybe_ready()?; // propagate Poll error
    let c: T = (e: Poll<T, E>)?; // propagate error and "not ready"
    let d: Result<T, E> = (e: Poll<T, E>).ready()?; // propagate "not ready"
    let e: T = (e: Async<T>)?; // propagate "not ready"
}
Member

alexcrichton commented Apr 24, 2017

Whoa nice investigation @colin-kiegel! Unfortunately though I think that once you start splitting it up at the crate boundary it runs afoul of coherence :(

error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g. `MyStruct<T>`); only traits defined in the current crate can be implemented for a type parameter
  --> bar.rs:60:5
   |
60 |     impl<T, E> TryFrom<Poll<T, E>> for T {
   |     ^

error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g. `MyStruct<T>`); only traits defined in the current crate can be implemented for a type parameter
   --> bar.rs:103:5
    |
103 |     impl<T, E> TryFrom<Poll<T, E>> for Result<T, E> {
    |     ^

error: aborting due to 2 previous errors

I wonder, though, if using short methods (like poll.ready()?) would still allow this to be possible? For example maybe:

fn foo() -> Poll<T, E> {
    let a: T = (e: Result<T, E>)?; // propagate Result error
    let b: Async<T> = (e: Poll<T, E>).maybe_ready()?; // propagate Poll error
    let c: T = (e: Poll<T, E>)?; // propagate error and "not ready"
    let d: Result<T, E> = (e: Poll<T, E>).ready()?; // propagate "not ready"
    let e: T = (e: Async<T>)?; // propagate "not ready"
}

@carols10cents carols10cents added this to FCP in Tracker Apr 26, 2017

@bstrie

This comment has been minimized.

Show comment
Hide comment
@bstrie

bstrie May 3, 2017

Contributor

In the interest of keeping this moving, would we like to FCP again, or do we think more discussion is necessary? A new summary of where we stand would be instructive ( @nikomatsakis ?).

Contributor

bstrie commented May 3, 2017

In the interest of keeping this moving, would we like to FCP again, or do we think more discussion is necessary? A new summary of where we stand would be instructive ( @nikomatsakis ?).

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon May 3, 2017

Member

@bstrie Good idea.

@rfcbot fcp cancel

Member

aturon commented May 3, 2017

@bstrie Good idea.

@rfcbot fcp cancel

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon May 3, 2017

Member

@rfcbot fcp merge

I'm bringing this up for a fresh FCP, since the content of the RFC has switched approaches. You can get the gist of the new approach from @nikomatsakis's summary comment.

Member

aturon commented May 3, 2017

@rfcbot fcp merge

I'm bringing this up for a fresh FCP, since the content of the RFC has switched approaches. You can get the gist of the new approach from @nikomatsakis's summary comment.

@rfcbot

This comment has been minimized.

Show comment
Hide comment
@rfcbot

rfcbot May 3, 2017

@aturon proposal cancelled.

rfcbot commented May 3, 2017

@aturon proposal cancelled.

@rfcbot

This comment has been minimized.

Show comment
Hide comment
@rfcbot

rfcbot May 3, 2017

Team member @aturon has proposed to merge this. The next step is review by the rest of the tagged teams:

No concerns currently listed.

Once these reviewers reach consensus, this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

rfcbot commented May 3, 2017

Team member @aturon has proposed to merge this. The next step is review by the rest of the tagged teams:

No concerns currently listed.

Once these reviewers reach consensus, this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@mitsuhiko

This comment has been minimized.

Show comment
Hide comment
@mitsuhiko

mitsuhiko May 4, 2017

Contributor

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.

Contributor

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

Show comment
Hide comment
@nikomatsakis

nikomatsakis May 4, 2017

Contributor

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

Contributor

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

Show comment
Hide comment
@stbuehler

stbuehler 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

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.

Show comment
Hide comment
@nrc

nrc May 17, 2017

Member

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

Member

nrc commented May 17, 2017

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

@pnkfelix

This comment has been minimized.

Show comment
Hide comment
@pnkfelix

pnkfelix May 17, 2017

Member

@rfcbot reviewed

Member

pnkfelix commented May 17, 2017

@rfcbot reviewed

@rfcbot

This comment has been minimized.

Show comment
Hide comment
@rfcbot

rfcbot May 17, 2017

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

rfcbot commented May 17, 2017

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

@rfcbot

This comment has been minimized.

Show comment
Hide comment
@rfcbot

rfcbot May 27, 2017

The final comment period is now complete.

rfcbot commented May 27, 2017

The final comment period is now complete.

@repax

(typo)

Show outdated Hide outdated text/0000-try-trait.md
```rust
fn foo() -> Poll<T, E> {
let x = bar()?; // propogate error case

This comment has been minimized.

@repax

repax May 27, 2017

"propagate"

@repax

repax May 27, 2017

"propagate"

Show outdated Hide outdated text/0000-try-trait.md
```rust
fn foo() -> Result<T, E> {
let x = bar()?; // propogate error case

This comment has been minimized.

@repax

repax May 27, 2017

"propagate"

@repax

repax May 27, 2017

"propagate"

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

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis May 30, 2017

Contributor

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

Contributor

nikomatsakis 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 #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 #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 #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