Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Figure out a way to ergonomically match against impl traits? #2261

Closed
mqudsi opened this issue Dec 25, 2017 · 6 comments
Closed

Figure out a way to ergonomically match against impl traits? #2261

mqudsi opened this issue Dec 25, 2017 · 6 comments
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@mqudsi
Copy link

mqudsi commented Dec 25, 2017

impl trait as currently implemented with anonymized types is more of syntactic sugar than anything else, the inability to match against functions returning the same impl trait is.. unfortunate. This becomes painfully obvious when trying to work with futures-rs, which expresses the concept of a "future" as a trait but each future transform results in a distinct type that is not compatible from other future types.

For example, there (imho) needs to be a way for this code to compile without inane/insane workarounds:

(source code available at https://git.neosmart.net/mqudsi/futuretest/src/branch/rfcs-2261)

#![feature(conservative_impl_trait)]

extern crate futures;
extern crate tokio_core;
use futures::future::{self};
use futures::future::*;
use tokio_core::reactor::Core;

enum ErrorCode
{
    Case1,
    Case2,
}

fn main() {
    let mut core = Core::new().expect("Failed to initialize tokio_core reactor!");

    let f = async_entry_point()
        .or_else(move |err| {
            //the problem is that the three matches below resolve to different anonymized types
            match err {
                ErrorCode::Case1 => case1(),
                ErrorCode::Case2 => case2(),
            }
        })
    ;

    core.run(f);
}

fn async_entry_point() -> impl Future<Item=(), Error=ErrorCode> {
    future::ok(())
}

fn case1() -> impl Future<Item=(), Error=ErrorCode> {
    future::ok(())
}

fn case2() -> impl Future<Item=(), Error=ErrorCode> {
    future::ok(())
}

which currently does not compile because while the two functions return an object implementing the same trait, the compiler generates two wholly distinct anonymized types, one for each function:

   Compiling futuretest v0.1.0 (file:///mnt/d/GIT/futuretest)
error[E0308]: match arms have incompatible types
  --> src/main.rs:21:13
   |
21 | /             match err {
22 | |                 ErrorCode::Case1 => case1(),
23 | |                 ErrorCode::Case2 => case2(),
24 | |             }
   | |_____________^ expected anonymized type, found a different anonymized type
   |
   = note: expected type `impl futures::Future` (anonymized type)
              found type `impl futures::Future` (anonymized type)
note: match arm with an incompatible type
  --> src/main.rs:23:37
   |
23 |                 ErrorCode::Case2 => case2(),
   |                                     ^^^^^^^

error: aborting due to previous error

error: Could not compile `futuretest`.

To learn more, run the command again with --verbose.

I can think of one very simple solution to this, but I can't write it out due to the anonymized types: for a match block where all the entries are anonymized types implementing type X or else standard types implementing type X, create a new, anonymized enum type E with a case for each, convert each anonymized type implementing X to E::{X1,X2,X3,..,Xn}, and then decompose the value at the point where the result bubbles up to a comparison that must be fully typed to succeed.

In retrospect, this is actually not specific to impl trait any more, can be used to match against n types sharing one or more traits.

@Centril Centril added the T-lang Relevant to the language team, which will review and decide on the RFC. label Dec 25, 2017
@Centril
Copy link
Contributor

Centril commented Dec 25, 2017

Perhaps we could use a #[fundamental] Coproduct for this as well as https://docs.rs/frunk/0.1.34/frunk/coproduct/trait.CoprodInjector.html#tymethod.inject?

@SimonSapin
Copy link
Contributor

Perhaps a first step might be the ability to name the concrete type of a function that returns impl Trait, for example something like <case1 as Fn<()>>::Return

@burdges
Copy link

burdges commented Dec 25, 2017

Right now, coproduct types are called Either in Rust, like in Haskell. See futures and iterators. We've had several discussion about adding nice syntax for anonymous sum, coproducts, etc. types, like #294

There are separate discussion around both fn::Ouput and trait MyTrait { type Foo = impl Trait; .. or the like. #1879

@Centril
Copy link
Contributor

Centril commented Dec 25, 2017

Right now, coproduct types are called Either in Rust, like in Haskell. See futures and iterators.

I don't want to derail the issue with this side-discussion... But that is simply not true. Either is not part of libstd and as such it is not called anything in Rust, except you know enum. Frunk calls these Coproduct and has a series of smart traits to inject data into or from a sum type of an unbounded amount of variants.

We wouldn't necessarily need anonymous sum types... you might be able to do it with:

impl<A, B> Future for Coproduct<A, B> // Coherence troubles today. so #[fundamental]
where
    A: Future,
    B: Future<Item = A::Item, Error = A::Error>
{
    type Item = A::Item;
    type Error = A::Error;
    fn poll(&mut self) -> Poll<A::Item, A::Error> {
        match *self {
            Coproduct::Inl(ref mut a) => a.poll(),
            Coproduct::Inr(ref mut b) => b.poll(),
        }
    }
}
let f = async_entry_point().or_else(move |err| match err {
    ErrorCode::Case1 => case1().inject(), // Explicitly inject(), you can add 100 more variants if you like.
    ErrorCode::Case2 => case2().inject(),
});

@cramertj
Copy link
Member

cramertj commented Jan 5, 2018

futures-rs has an Either type for exactly this purpose. We've also discussed the possibility of allowing anonymous union types like u64 | u8, and automatically implementing some traits for these unions. A system like that could allow MyFuture1 | MyFuture2 to be an impl Future<...>.

@Centril
Copy link
Contributor

Centril commented Oct 9, 2018

Closing in favor of #2414.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests

5 participants