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
}