Skip to content

Tracking issue for ops::Try (try_trait feature) #42327

Closed

Description

@scottmcm

View all comments

Feature gate: #![feature(try_trait)]

This is a tracking issue for the Try trait from rust-lang/rfcs#1859.

Split off from #31436 for clarity (per #42275 (comment))

Public API

pub mod core {
    pub mod result {
        impl<T, E> ops::Try for Result<T, E> {
            type Ok = T;
            type Error = E;
            fn into_result(self) -> Self {}
            fn from_ok(v: T) -> Self {}
            fn from_error(v: E) -> Self {}
        }
    }

    pub mod option {
        #[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
        pub struct NoneError;

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

    pub mod ops {
        mod r#try {
            pub trait Try {
                type Ok;
                type Error;
                fn into_result(self) -> Result<Self::Ok, Self::Error>;
                fn from_error(v: Self::Error) -> Self;
                fn from_ok(v: Self::Ok) -> Self;
            }
        }

        pub use self::r#try::Try;
    }
}

Steps / History

  • Stabilizing this will allow people to implement Iterator::try_fold

    • As part of stabilizing, re-open PR Document implementing try_fold for iterators for internal iteration #62606 to document implementing try_fold for iterators
    • Ensure that the default implementations of other things have the desired long-term DAG, since changing them is essentially impossible later. (Specifically, it would be nice to have fold be implemented in terms of try_fold, so that both don't need to be overridden.)
    • Moved these to the try_trait_v2 tracking issue
  • Lower ? to Try instead of Carrier #42275

  • Deprecate and remove these in favour of the new versions.

Unresolved Questions

These resulted in a new rust-lang/rfcs#3058, tracked in #84277

Activity

  1. added
    B-RFC-implementedBlocker: Approved by a merged RFC and implemented but not stabilized.
    B-unstableBlocker: Implemented in the nightly compiler and unstable.
    on May 31, 2017
  2. glaebhoerl commented on Jun 28, 2017

    @glaebhoerl
    Contributor

    A couple of pieces of bikeshedding:

    • Do we have a particular motivation to call the associated type Error instead of Err? Calling it Err would make it line up with Result: the other one is already called Ok.

    • Do we have a particular motivation for having separate from_error and from_ok methods, instead of a single from_result which would be more symmetric with into_result which is the other half of the trait?

    (updated version of playground link from the RFC)

  3. scottmcm commented on Jun 28, 2017

    @scottmcm
    MemberAuthor

    @glaebhoerl

    • Error vs Err was discussed related to TryFrom in Tracking issue for TryFrom/TryInto traits #33417 (comment) and Rename TryFrom's associated type and implement str::parse using TryFrom. #40281; I assume the name was chosen here for similar reasons.
    • I believe they're separate because they have different uses, and I expect it to be rare that someone actually has a Result that they're trying to turn into a T:Try. I prefer Try::from_ok and Try::from_error to always calling Try::from_result(Ok( and Try::from_result(Err(, and I'm happy to just impl the two methods over writing out the match. Perhaps that's because I think of into_result not as Into<Result>, but as "was it pass or fail?", with the specific type being Result as an unimportant implementation detail. (I don't want to suggest or reopen the "there should be a new type for produce-value vs early-return, though.) And for documentation, I like that from_error talks about ? (or eventually throw), while from_ok talks about success-wrapping (catch blocks are not Ok-wrapping their value #41414), rather than having them both in the same method.
  4. fluffysquirrels commented on Jun 30, 2017

    @fluffysquirrels

    I'm not sure if this is the correct forum for this comment, please redirect me if it's not 😃. Perhaps this should have been on rust-lang/rfcs#1859 and I missed the comment period; oops!


    I was wondering if there is a case for splitting up the Try trait or removing the into_result method; to me currently Try is somewhat like the sum of traits FromResult (containing from_error and from_ok) and IntoResult (containing into_result).

    FromResult enables highly ergonomic early exit with the ? operator, which I think is the killer use case for the feature. I think IntoResult can already be implemented neatly with per-use-case methods or as Into<Result>; am I missing some useful examples?

    Following the Try trait RFC, expr? could desugar as:

    match expr { // Removed `Try::into_result()` here.
        Ok(v) => v,
        Err(e) => return Try::from_error(From::from(e)),
    }

    The motivating examples I considered are Future and Option.

    Future

    We can implement FromResult naturally for struct FutureResult: Future as:

    impl<T, E> FromResult for FutureResult {
        type Ok = T;
        type Error = E;
        fn from_error(v: Self::Error) -> Self {
            future::err(v)
        }
        fn from_ok(v: Self::Ok) -> Self {
            future::ok(v)
        }
    }

    Assuming a reasonable implementation for ? that will return from an impl Future valued function when applied to a Result::Err then I can write:

    fn async_stuff() -> impl Future<V, E> {
        let t = fetch_t();
        t.and_then(|t_val| {
            let u: Result<U, E> = calc(t_val);
            async_2(u?)
        })
    }
    fn fetch_t() -> impl Future<T, E> {}
    fn calc(t: T) -> Result<U,E> {}

    That is exactly what I was trying to implement earlier today and Try nails it! But if we tried to implement the current Try for Future there is perhaps no canonical choice for into_result; it could be useful to panic, block, or poll once, but none of these seems universally useful. If there were no into_result on Try I can implement early exit as above, and if I need to convert a Future to a Result (and thence to any Try) I can convert it with a suitable method (call the wait method to block, call some poll_once() -> Result<T,E>, etc.).

    Option

    Option is a similar case. We implement from_ok, from_err naturally as in the Try trait RFC, and could convert Option<T> to Result<T, Missing> simply with o.ok_or(Missing) or a convenience method ok_or_missing.


    Hope that helps!

  5. added
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    on Jul 22, 2017
  6. skade commented on Jul 30, 2017

    @skade
    Contributor

    I'm maybe very late to all this, but it occured to me this weekend that ? has a rather natural semantic in cases where you'd like to short-circuit on success.

    fn fun() -> SearchResult<Socks> {
        search_drawer()?;
        search_wardrobe()
    }

    But in this case, the naming of the Try traits methods don't fit.

    It would widen the meaning of the ? operator, though.

  7. cuviper commented on Oct 25, 2017

    @cuviper
    Member

    In prototyping a try_fold for rayon, I found myself wanting something like Try::is_error(&self) for the Consumer::full() methods. (Rayon has this because consumers are basically push-style iterators, but we still want to short-circuit errors.) I instead had to store the intermediate values as Result<T::Ok, T::Err> so I could call Result::is_err().

    From there I also wished for a Try::from_result to get back to T at the end, but a match mapping to T::from_ok and T::from_error isn't too bad.

  8. fluffysquirrels commented on Oct 28, 2017

    @fluffysquirrels

    The Try trait can provide a from_result method for ergonomics if from_ok and from_error are required. Or vice versa. From the examples given I see a case for offering both.

  9. ErichDonGubler commented on Dec 29, 2017

    @ErichDonGubler
    Contributor

    Since PR #42275 has been merged, does that mean this issue has been resolved?

  10. scottmcm commented on Dec 29, 2017

    @scottmcm
    MemberAuthor

    @skade Note that a type can define whichever as the short-circuiting one, like LoopState does for some Iterator internals. Perhaps that would be more natural if Try talked about "continuing or breaking" instead of success or failure.

    @cuviper The version I ended up needing was either the Ok type or the Error-in-original type. I'm hoping that destructure-and-rebuild is a general enough thing that it'll optimize well and a bunch of special methods on the trait won't be needed.

    @ErichDonGubler This is a tracking issue, so isn't resolved until the corresponding code is in stable.

  11. nikomatsakis commented on Feb 20, 2018

    @nikomatsakis
    Contributor

    Experience report:

    I've been a little frustrated trying to put this trait into practice. Several times now I've been tempted to define my own variant on Result for whatever reason, but each time I've wound up just using Result in the end, mostly because implementing Try was too annoying. I'm not entirely sure if this is a good thing or not, though!

    Example:

    In the new solver for the Chalk VM, I wanted to have an enum that indicates the result of solving a "strand". This had four possibilities:

    enum StrandFail<T> {
        Success(T),
        NoSolution,
        QuantumExceeded,
        Cycle(Strand, Minimums),
    }

    I wanted ?, when applied to this enum, to unwrap "success" but propagated all other failures upward. However, in order to implement the Try trait, I would have had to define a kind of "residual" type that encapsulates just the error cases:

    enum StrandFail {
        NoSolution,
        QuantumExceeded,
        Cycle(Strand, Minimums),
    }

    But once I've got this type, then I might as well make StrandResult<T> an alias:

    type StrandResult<T> = Result<T, StrandFail>;

    and this is what I did.

    Now, this doesn't necessarily seem worse than having a single enum -- but it does feel a bit odd. Usually when I write the documentation for a function, for example, I don't "group" the results by "ok" and "error", but rather talk about the various possibilities as "equals". For example:

        /// Invoked when a strand represents an **answer**. This means
        /// that the strand has no subgoals left. There are two possibilities:
        ///
        /// - the strand may represent an answer we have already found; in
        ///   that case, we can return `StrandFail::NoSolution`, as this
        ///   strand led nowhere of interest.
        /// - the strand may represent a new answer, in which case it is
        ///   added to the table and `Ok` is returned.
    

    Note that I didn't say "we return Err(StrandFail::NoSolution). This is because the Err just feels like this annoying artifact I have to add.

    (On the other hand, the current definition would help readers to know what the behavior of ? is without consulting the Try impl.)

    I guess this outcome is not that surprising: the current Try impl forces you to use ? on things that are isomorphic to Result. This uniformity is no accident, but as a result, it makes it annoying to use ? with things that are not basically "just Result". (For that matter, it's basically the same problem that gives rise to NoneError -- the need to artificially define a type to represent the "failure" of an Option.)

    I'd also note that, in the case of StrandFail, I don't particularly want the From::from conversion that ordinary results get, though it's not hurting me.

  12. glaebhoerl commented on Feb 20, 2018

    @glaebhoerl
    Contributor

    Is the associated Try::Error type ever exposed to / used by the user directly? Or is it just needed as part of the desugaring of ?? If the latter, I don't see any real problem with just defining it "structurally" - type Error = Option<Option<(Strand, Minimums)>> or whatever. (Having to figure out the structural equivalent of the "failure half" of the enum definition isn't great, but seems less annoying than having to rejigger the whole public-facing API.)

  13. kevincox commented on Feb 20, 2018

    @kevincox
    Contributor

    I don't follow either. I have successfully implemented Try for a type that had an internal error representation and it felt natural having Ok and Error being the same type. You can see the implementation here: https://github.com/kevincox/ecl/blob/8ca7ad2bc4775c5cfc8eb9f4309b2666e5163e02/src/lib.rs#L298-L308

    In fact is seems like there is a fairly simple pattern for making this work.

    impl std::ops::Try for Value {
    	type Ok = Self;
    	type Error = Self;
    	
    	fn from_ok(v: Self::Ok) -> Self { v }
    	fn from_error(v: Self::Error) -> Self { v }
    	fn into_result(self) -> Result<Self::Ok, Self::Error> {
    		if self.is_ok() { Ok(val) } else { Err(val) }
    	}
    }

    If you want to unwrap the success something like this should work:

    impl std::ops::Try for StrandFail<T> {
    	type Ok = T;
    	type Error = Self;
    	
    	fn from_ok(v: Self::Ok) -> Self { StrandFail::Success(v) }
    	fn from_error(v: Self::Error) -> Self { v }
    	fn into_result(self) -> Result<Self::Ok, Self::Error> {
    		match self {
    			StrandFail::Success(v) => Ok(v),
    			other => Err(other),
    		}
    	}
    }
  14. nikomatsakis commented on Feb 20, 2018

    @nikomatsakis
    Contributor

    Defining a structural type is possible but feels pretty annoying. I agree I could use Self. It feels a bit wacky to me though that you can then use ? to convert from StrandResult to Result<_, StrandResult> etc.

  15. 89 remaining items

  16. thomcc commented on Oct 3, 2020

    @thomcc
    Member

    but will panic on None

    Strongly feel that we should just leave this to unwrap. If we added a todo_err it should return an actual error type, which begs the question of what error type to return.

    Also I suspect fn todo_err(self) -> Result<Self, !> would have the obvious problem of requiring ! to stabilize, which uh, well, Someday.

  17. PvdBerg1998 commented on Oct 4, 2020

    @PvdBerg1998

    My usecase is indeed rough prototyping where I don't care about the errors too much. Doesn't the whole NoneError suffer from these problems you listed? If it is decided that it should exist (which I think is a good thing, at least for prototyping), I believe it should impl Error as it is named to be one.

    Because of the type lacking this impl I resorted to just slapping a .ok_or("error msg") on it, which works also but is less convenient.

  18. added a commit that references this issue on Oct 19, 2020
  19. smmalis37 commented on Dec 1, 2020

    @smmalis37
    Contributor

    Being unable to cast a NoneError to a dyn std::error:Error definitely feels like an ergonomics roadbump to me. I get that there's no great message that could be put in it, but if I'm just throwing everything into a trait object I probably don't care too much about the details of each error to begin with. It'd be really nice for prototype code to be able to return Result<Foo, Box<dyn Error>> everywhere and have ? work with Option. Given that I am explicitly declaring that I don't care much about the errors right now, having to call .ok_or or some other adapter every time would really get in the way of the "quick prototyping" ideal.

  20. thomcc commented on Dec 1, 2020

    @thomcc
    Member

    Being unable to cast a NoneError to a dyn std::error:Error definitely feels like an ergonomics roadbump to me

    I don't think this is being debated, so much as it locking us into the current (clearly suboptimal) design in some ways.

  21. scottmcm commented on Jan 10, 2021

    @scottmcm
    MemberAuthor

    For anyone following along here: I've posted a new RFC with an alternative Try/? design at rust-lang/rfcs#3058

  22. scottmcm commented on Apr 17, 2021

    @scottmcm
    MemberAuthor

    @yaahc I was going through the checkboxes here, and wanted to check whether you'd be fine saying that the one referencing your comment #42327 (comment) is resolved by the new RFC.

  23. yaahc commented on Apr 19, 2021

    @yaahc
    Member

    @yaahc I was going through the checkboxes here, and wanted to check whether you'd be fine saying that the one referencing your comment #42327 (comment) is resolved by the new RFC.

    Definitely 👍

  24. bstrie commented on Apr 20, 2021

    @bstrie
    Contributor

    Now that the Try v2 RFC has been accepted, shall we close this in favor of #84277 ?

  25. scottmcm commented on Apr 20, 2021

    @scottmcm
    MemberAuthor

    @bstrie There's still #[unstable] attributed in-tree referencing this, so I think this should stay open until those items have all been deleted. I added a checkbox to the OP for that.

  26. scottmcm commented on Sep 1, 2021

    @scottmcm
    MemberAuthor

    Try v1 has been completely removed from master (https://github.com/rust-lang/rust/search?q=42327&type=code).

  27. added
    I-libs-radarLibs issues that are tracked on the team's radar.
    and removed
    I-libs-radarLibs issues that are tracked on the team's radar.
    on Oct 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-error-handlingArea: Error handlingB-RFC-implementedBlocker: Approved by a merged RFC and implemented but not stabilized.B-unstableBlocker: Implemented in the nightly compiler and unstable.C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCI-libs-radarLibs issues that are tracked on the team's radar.T-langRelevant to the language teamT-libs-api[DEPRECATED; DO NOT USE]

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions