在 async fn 中实现 FnOnce 不够通用?

gav*_*rie 8 rust

以下代码 ( playground ) 无法编译:

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);
}
Run Code Online (Sandbox Code Playgroud)

错误信息是:

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}`
Run Code Online (Sandbox Code Playgroud)

我发现了一些提到 HRTB 的类似项目(例如这个 Rust 问题这个),并尝试尝试使用for<'a>额外的生命周期界限,但我仍然不清楚错误。

我不明白为什么编译器不能确定生命周期,特别是因为没有期货的简单版本编译得很好:

fn myfunc(_: &i32) {}

fn wrapper<F: FnOnce(&i32)>(func: F) {
    let i = 5;
    func(&i);
}

fn main() {
    wrapper(myfunc);
}
Run Code Online (Sandbox Code Playgroud)

所以,我想问一下:

  1. 这里的实际问题是什么?
  2. 是否有一种解决方法可以使此编译,例如具有附加边界?

pro*_*-fh 2

从您提供的问题中解释的内容开始,可以为您的包装器期望的功能创建特定的特征。

作为我自己对 Rust 相当陌生,我不知道为什么在你的原始版本中,编译器会抱怨显示的错误消息中实际上等效的不匹配类型(当我们有显式生命周期时,这一点更加明显;显示的类型完全匹配) !)。我只是猜测这是因为确切的结果类型隐藏在符号后面impl,而实际的差异在于这个隐藏的部分。

确实很难表达参数和结果都应该考虑任何生命周期,因为它们出现在子句的两个不同约束上。据我了解(不是那么多......),考虑整个特征的显式生命周期使得参数和结果的表示法一致,因为所有这些都处于子句的相同约束中。for<'r>&Futurewherefor<'r>&Futurewhere

由于我们正在处理生命周期,因此我决定将可复制的替换i32为不可复制的String,以防止发生任何意外的简化。

正如您提供的链接中所述,该解决方案似乎仅适用于函数,不适用于闭包。

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);
}
Run Code Online (Sandbox Code Playgroud)