Skip to content

Tracking Issue for Result::flatten (result_flattening) #70142

Closed

Description

@Nemo157

This is a tracking issue for the Result::flatten API.
The feature gate for the issue is #![feature(result_flattening)].

impl<T, E> Result<Result<T, E>, E> {
      pub fn flatten(self) -> Result<T, E>;
}

About tracking issues

Tracking issues are used to record the overall progress of implementation.
They are also uses as hubs connecting to other relevant issues, e.g., bugs or open design questions.
A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature.
Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.

Steps

Unresolved Questions

Implementation history

Activity

  1. added
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    on Mar 19, 2020
  2. added
    B-unstableBlocker: Implemented in the nightly compiler and unstable.
    T-libs-api[DEPRECATED; DO NOT USE]
    on Mar 19, 2020
  3. added a commit that references this issue on Mar 29, 2020
  4. added
    A-result-optionArea: Result and Option combinators
    Libs-SmallLibs issues that are considered "small" or self-contained
    I-libs-radarLibs issues that are tracked on the team's radar.
    on Jul 29, 2020
  5. josephlr commented on Sep 12, 2020

    @josephlr
    Contributor

    With the stabilization of #49146, this function can now have a trivial const fn implementation using match. Should this function just be directly stabilized as a const fn?

  6. Diggsey commented on Sep 28, 2020

    @Diggsey
    Contributor

    I think it would also be useful to have a flatten_into method that would also allow conversion of the error type.

  7. de-vri-es commented on Oct 7, 2020

    @de-vri-es
    Contributor

    It could also be

    impl<T, E1, E2> for Result<Result<T, E1>, E2>
    where
      E1: From<E2>,
    {
      fn flatten(self) -> Result<T, E1> {
        self?
      }
    }

    But: which error type do you convert into the other?

    Keeping the inner error type seemed more logical to me, under the assumption that the inner thing is what you really want, and the outer result represents further processing.

    An example where that occurs is using tokio::time::timeout to add a timeout to I/O operations. The result has an Elapsed error that can be converted into an std::io::Error, but not vice-versa.

  8. yoshuawuyts commented on Oct 12, 2020

    @yoshuawuyts
    Member

    In #70140 (comment) @CryZe brought up:

    Should this possibly also convert the error type like the try operator does?

    This seems to touch on the same point as @Diggsey's and @de-vri-es's comments on converting error types. And it's easy to see why; the ? operator just works when one error can be converted into another, and it's not unreasonable to expect other methods that deal with converting error types to work much the same.

    The only argument against this so far has been #70140 (comment) which talks about monadic similarities between Option and Result. I don't find that argument particularly appealing, as it centers a subjective theoretical view on what I think is an ergonomics question. I think it's far preferable for Rust to feel internally consistent to over consistency with a monadic model.

    I think the implementation of @de-vri-es looks right. I intend to open a PR shortly to implement this

  9. Nemo157 commented on Oct 12, 2020

    @Nemo157
    ContributorAuthor

    I don't know that E1: From<E2> is the correct order, my instinct would have been the inverse. It seems like getting some examples of usecases would be good to see how each order affects it?

    (Though personally I don't think the base .flatten() method should do conversion, and having a separate .flatten_into() might be a good approach).

    One example for when you have two incompatible error types, and you have to convert into a third error type:

    // with `E1: From<E2>`
    result.map(|res| res.map_err(anyhow::Error::from)).flatten_into()?;
    // with `E2: From<E1>`
    result.map_err(anyhow::Error::from).flatten_into()?;

    For maximum ergonomics, with improved defaults and ? type inferencing it could even be something like

    impl<T, E1, E2> for Result<Result<T, E1>, E2> {
      fn flatten_into<E = E1/E2>(self) -> Result<T, E> where E: From<E2>, E: From<E1> {
        Ok(self??)
      }
    }

    which would allow converting two incompatible error types into a final error type that they are both compatible with

    fn foo() -> Result<Result<(), impl Error>, impl Error> { ... }
    
    fn foobar() -> anyhow::Result<()> {
      foo().flatten_into()?
    }
  10. yoshuawuyts commented on Oct 12, 2020

    @yoshuawuyts
    Member

    I don't know that E1: From is the correct order, my instinct would have been the inverse. It seems like getting some examples of usecases would be good to see how each order affects it?

    Oh yeah good one. I guess the one I had in mind was the one @de-vri-es mentioned with an async timeout method. For example in async-std the API would be the following (ref):

    use async_std::{fs, future};
    use std::time::Duration;
    
    let dur = Duration::from_millis(100);
    let s = fs::read_to_string("./my.db").timeout(dur).await??;

    The return type here is

    Result<Result<String, io::Error>, future::Timeout>
    Result<Result<String, E1>, E2>
    

    We'd want to convert from future::Timeout to io::Error, or convert E2 into E1 -- which the implementation does. But I'd be curious to know if there are any practical counter-examples.


    I think you're asking an intriguing question: what if both errors could somehow converge into some third error type? You suggest using flatten_into for this; but that raises two questions:

    1. Should the existence of Result::flatten_into forbid Result::flatten from having an Into bound? I would assume not?
    2. To which degree do flatten and flatten_into overlap? Assuming we can declare the default of fn flatten_into<E = E1>, having both seems redundant?
  11. Diggsey commented on Oct 12, 2020

    @Diggsey
    Contributor

    FWIW, the specific case where I encountered this requires the full flatten_into version where a 3rd error type is specified.

    That said, I think there is value in both, as flatten_into will usually require explicit type annotations, whereas the variations of flatten should for the most part be able to infer their types, and so will be more ergonomic where it is possible to use them.

  12. de-vri-es commented on Oct 12, 2020

    @de-vri-es
    Contributor

    That said, I think there is value in both, as flatten_into will usually require explicit type annotations, whereas the variations of flatten should for the most part be able to infer their types, and so will be more ergonomic where it is possible to use them.

    Agreed.

    The E1 vs E2 issue might be enough reason to only support a single error type without conversion for flatten(). Or have a flatten_inner() and flatten_outer(), but maybe that is a bit overkill.

    /edit: Also, you can't have defaults for generic type arguments for functions, right? So that won't be a solution (see also #36887).

  13. 22 remaining items

  14. Rynibami commented on May 15, 2025

    @Rynibami

    I think this feature is neat to have on stable rust, but I haven't seen any clear progression on this topic except for @RalfJung's const fn pull request. Can I assume that the provided steps (see quote) describe all remaining work before it can be integrated with stable rust?

    Steps

    If that is all, then I would like to contribute by adjusting the documentation and setting up a stabilization PR. I might need some help though as I am new to the rust project.

  15. RalfJung commented on May 15, 2025

    @RalfJung
    Member

    @Rynibami this is an ancient tracking issue so the template is a bit misleading, sorry for that.

    The next step here would be to make a PR that stabilizes the feature. Make sure that the PR description contains a summary of what is being stabilized, in particular including the function signature. It also seems like the corresponding method on Option is already stable for a while which seems worth mentioning.

    Then we can nominate that PR for discussion by the relevant team.

  16. Rynibami commented on May 16, 2025

    @Rynibami

    Thank you very much for elaborating on my comment @RalfJung.

    I've looked into the implementation of both Option::flatten and Result::flatten. They are identical except for the implications of the E error type (Result<Result<T, E>, E>). So this looks like I do not have to write any new code, changing only compiler flags and create documentation.

    The flatten function in option.rs

    #[inline]
    #[stable(feature = "option_flattening", since = "1.40.0")]
    #[rustc_allow_const_fn_unstable(const_precise_live_drops)]
    #[rustc_const_stable(feature = "const_option", since = "1.83.0")]
    pub const fn flatten(self) -> Option<T> {
        // FIXME(const-hack): could be written with `and_then`
        match self {
            Some(inner) => inner,
            None => None,
        }
    }

    The flatten function in result.rs

    #[inline]
    #[unstable(feature = "result_flattening", issue = "70142")]
    #[rustc_const_unstable(feature = "result_flattening", issue = "70142")]
    pub const fn flatten(self) -> Result<T, E> {
        // FIXME(const-hack): could be written with `and_then`
        match self {
            Ok(inner) => inner,
            Err(e) => Err(e),
        }
    }

    I can make a new PR that highlights the already implemented flatten function, update the compiler flags and documentation, and follow the book on stabilizing features. Is this the complete way and correct order to do this? Or am I missing something?

  17. RalfJung commented on May 18, 2025

    @RalfJung
    Member

    Yes that is correct. :)

  18. added 2 commits that reference this issue on May 31, 2025
  19. added a commit that references this issue on Jun 1, 2025
  20. rursprung commented on Jul 10, 2025

    @rursprung
    Contributor

    it seems that #141072 stabilized Result::flatten in the upcoming 1.89 release. what i haven't read is what the goal is regarding the flatten_into discussion above? will this be split out into a new issue?

    i just ran into this when working with postcard-rpc (an remote procedure call library for embedded systems) where you can end up with Result<T, Ebusiness>, Etransport>: you always get a Result<T, Etransport> where Etransport is the error coming from the transport layer (e.g. an error during USB communication with the target device) and T is fully under your control. now it's quite likely that you might want to surface some (business) errors from the underlying device in the RPC call, so instead of, say, returning an u8 you might return Result<u8, MyError>, in which case you'll end up with Result<Result<u8, MyError>, HostErr<WireError>>.

    usually you wrap these calls in an own function to abstract away the RPC communication. in that case you'll probably implement an own error type to return there (e.g. you might return Result<u8, MyLibraryError>) and add an impl From<MyError> for MyLibraryError (and the same for the other error type).

    the example has a custom flatten implementation: https://github.com/jamesmunns/postcard-rpc/blob/10a1f6a6697c556eb07d9dec6bfd804e14830c5b/example/workbook-host/src/client.rs#L30-L42
    i think it'd be great if we could also get flatten_into (or similar) in core

  21. RalfJung commented on Jul 10, 2025

    @RalfJung
    Member

    Good point, this should have been closed by #141072.

    Regarding flatten_into, that did not make it into this feature. It would be a completely new API; the process for proposing that is to file an ACP with the libs-api team.

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-result-optionArea: Result and Option combinatorsB-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.Libs-SmallLibs issues that are considered "small" or self-containedT-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