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
}