The following code (playground) fails to compile:
async fn wrapper<F, Fut>(func: F)
where
F: FnOnce(&i32) -> Fut,
Fut: Future<Output = ()>,
{
let i = 5;
func(&i).await;
}
async fn myfunc(_: &i32) {}
fn main() {
wrapper(myfunc);
}
The error message is:
error: implementation of `std::ops::FnOnce` is not general enough
--> src/main.rs:15:5
|
15 | wrapper(myfunc);
| ^^^^^^^ implementation of `std::ops::FnOnce` is not general enough
|
= note: `std::ops::FnOnce<(&'0 i32,)>` would have to be implemented for the type `for<'_> fn(&i32) -> impl std::future::Future {myfunc}`, for some specific lifetime `'0`...
= note: ...but `std::ops::FnOnce<(&i32,)>` is actually implemented for the type `for<'_> fn(&i32) -> impl std::future::Future {myfunc}`
I have found a few similar items that mention Higher-Ranked Trait bounds (e.g. this Rust issue and this one), and tried experimenting with for<'a>
and additional lifetime bounds, but the error is still not clear to me.
I don't understand why the compiler can't determine the lifetimes, especially since a simpler version without futures compiles just fine:
fn myfunc(_: &i32) {}
fn wrapper<F: FnOnce(&i32)>(func: F) {
let i = 5;
func(&i);
}
fn main() {
wrapper(myfunc);
}
What is the actual problem here? Is there a workaround that can make this compile, e.g. with additional bounds?
Starting from what is explained in the issue you provided, a specific trait can be created for the functions your wrapper expects.
Being myself quite new to Rust, I don't know exactly why in your original version the compiler complains about mismatching types that are actually equivalent in the displayed error message (it is even more obvious when we had explicit lifetimes ; the displayed types match perfectly!).
I simply guess this is due to the fact that the exact result type is hidden behind an impl
notation and the actual difference lies in this hidden part.
It is indeed difficult to express that any lifetime for<'r>
should be considered for the &
parameter as well as for the Future
result
because they appear on two different constraints of the where
clause.
As far I understand (not so much...), considering an explicit lifetime for the whole trait makes the for<'r>
notation consistent for the &
parameter as well as the Future
result because all of this is in the same constraint of the where
clause.
Since we are dealing with lifetimes, I decided to replace the copyable i32
by a non-copyable String
in order to prevent any unexpected simplifications from happening.
As stated in the link you gave, this solution seems to work only with functions, not closures.
use std::future::Future;
async fn wrapper<F>(func: F)
where
F: for<'r> Wrapped<'r>,
{
let s = String::from("WRAPPED");
func.call_once(&s).await;
}
trait Wrapped<'a> {
type Res: Future<Output = ()>;
fn call_once(
self,
s: &'a String,
) -> Self::Res;
}
impl<'a, F, FutRes> Wrapped<'a> for F
where
F: FnOnce(&'a String) -> FutRes,
FutRes: Future<Output = ()> + 'a,
{
type Res = FutRes;
fn call_once(
self,
s: &'a String,
) -> Self::Res {
self(s)
}
}
async fn call_myfunc() {
let s = String::from("HARDCODED");
myfunc(&s).await;
}
async fn myfunc(arg: &String) {
println!("arg={}", arg);
}
fn main() {
println!("~~~~ hardcoded call ~~~~");
let f = call_myfunc();
futures::executor::block_on(f);
println!("~~~~ use wrapper() ~~~~");
let f = wrapper(myfunc);
futures::executor::block_on(f);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With