Error: distinct uses of `impl Trait` result in different opaque types

Well that’s just a note and not the “main” error message and it is certainly slightly confusing since you don’t write impl anywhere. The main error is `match` arms have incompatible types and this means that a and b have different types. Now what are the types of a or b?

A way to “desugar” the async function a

async fn a() -> u32 {
    1
}

is like this:

fn a() -> impl Future<Output=u32> {
    async {
        1
    }
}

there we got our impl.

What this means is that a() returns some type that is not further specified in the signature (but is going to be some concrete, in this case anonymous, type) that implements the trait Future. This type is generated by the async block. Now the return type of a is different from b, even though b looks really similar

fn b() -> impl Future<Output=u32> {
    async {
        b
    }
}

See, the reason they look similar is only that we don’t explicitly write the actual return type, (and neither could we, since it’s an anonymous type) so b() as well returns some type of future, but this future is clearly a different type of future. Why? Well, it behaves differently, it produces a 2 instead of a 1 when evaluated. (And even if it did behave the same, we must not rely on this because it must be possible to change the implementation of these “opaque” types without this change being problematic.)


Now on to the question how we could try to fit these two types back together into a single one. There’s two routes to go here—trait objects or enums.

Let’s consider trait objects first. The way to fit all kinds of impl Future into the same type is to convert them into dyn Future (roughtly speaking). I’ll just present some code that also uses Box and Pin. The Box because that’s a simple way to handle trait objects (and I couldn’t find a nice way to avoid it) and the Pin because you need it for Future for technical reasons. Code follows:

use std::future::Future;
use std::pin::Pin;

fn main() {
    let mut input = String::new();
    println!("input unsigned integer:");
    std::io::stdin()
        .read_line(&mut input)
        .expect("readline error");
    let func: fn() -> Pin<Box<dyn Future<Output=u32>>> = match input.trim() {
        "1" => || Box::pin(a()),
        _ => || Box::pin(b()),
    };
    func();
}

async fn a() -> u32 {
    1
}

async fn b() -> u32 {
    2
}

One thing to note is that the call to func() doesn’t do anything yet. I would suggest using tokio to allow for an async main function, and call func() with the necessary await to actually get a value out:

use std::future::Future;
use std::pin::Pin;

#[tokio::main]
async fn main() {
    let mut input = String::new();
    println!("input unsigned integer:");
    std::io::stdin()
        .read_line(&mut input)
        .expect("readline error");
    let func: fn() -> Pin<Box<dyn Future<Output=u32>>> = match input.trim() {
        "1" => || Box::pin(a()),
        _ => || Box::pin(b()),
    };
    let result: u32 = func().await;
}

async fn a() -> u32 {
    1
}

async fn b() -> u32 {
    2
}

The enum version uses, well, an enum. See, an enum is the way to combine multiple types. Want an int of float? Do this!

enum IntOrFloat {
    Int(i64),
    Float(f64),
}

Want either this opaque future type of that opaque future type that you both can’t name and you don’t actually care about the name of the enum either and would prefer not to have to implement a new one either and the best thing would be if like the dyn Future construct version the enum would also be a type that is a future itself?

Perhaps a bit too specific this question but the answer is, you can have it! It’s called Either (there’s multiple versions of this type throughout different crates, I’m referring to futures::future::Either which has different behavior around Pin than either::Either if I understand this correctly).

Without further ado, the code example, hope it helps:

use futures::future::Either::*;

#[tokio::main]
async fn main() {
    let mut input = String::new();
    println!("input unsigned integer:");
    std::io::stdin()
        .read_line(&mut input)
        .expect("readline error");
    let func = match input.trim() {
        "1" => || Left(a()),
        _ => || Right(b()),
    };
    let result: u32 = func().await;
}

async fn a() -> u32 {
    1
}

async fn b() -> u32 {
    2
}
1 Like