问题描述
我正在做执行器/反应器,却发现这是一生的问题。它与异步/未来无关,可以在没有异步糖的情况下进行复制。
use std::future::Future;
struct Runtime;
fn start_with_runtime<C,F>(closure: C)
where
C: for<'a> FnOnce(&'a Runtime) -> F,F: Future
{
let rt = Runtime;
let _future = closure(&rt);
// block_on(future);
}
async fn async_main(_rt: &Runtime) {
// I can use _rt to do async stuff here
}
fn main() {
start_with_runtime(|rt| { async_main(rt) });
}
我希望start_with_runtime()
运行将来的版本,并提供异步运行时引用作为参数。
它不能编译:
error: lifetime may not live long enough
--> src/main.rs:17:31
|
17 | start_with_runtime(|rt| { async_main(rt) });
| --- ^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
| | |
| | return type of closure is impl std::future::Future
| has type `&'1 Runtime`
我认为这个问题似乎是因为锈蚀如何推断出瓶盖的寿命:
https://github.com/rust-lang/rust/issues/58052:
fn main() {
let f = |x: &i32| x;
let i = &3;
let j = f(i);
}
也不编译:
error: lifetime may not live long enough
--> src/main.rs:2:23
|
2 | let f = |x: &i32| x;
| - - ^ returning this value requires that `'1` must outlive `'2`
| | |
| | return type of closure is &'2 i32
| let's call the lifetime of this reference `'1`
好像我的闭包签名推断为|&'a Runtime| -> impl Future + 'b
,因此导致生命周期错误。我认为给定正确的结束签名会有所帮助,但是如何在start_with_runtime
中提供正确的签名?
fn start_with_runtime<C>(closure: C)
where
C: for<'a> FnOnce(&'a Runtime) -> (impl Future + 'a),
不起作用,因为此处不允许使用impl Trait
。
fn start_with_runtime<C,F: Future + 'a
由于'a
在HRTB表达式之外并不为人所知,因此效果不佳。
如果我知道类型,它会起作用:
struct MyType<'a> {
_rt: &'a Runtime
}
fn start_with_runtime<C>(closure: C)
where
C: for<'a> FnOnce(&'a Runtime) -> MyType<'a>,
当您一生都在想着,但语言无法提供表达这种方式的方式时,这会让您感到悲伤。也许有一种使它起作用的生锈技巧?
解决方法
在这个问题中似乎存在两个不同的问题:所需的关系可以用Rust语法表示,并且可以与闭包类型推断一起使用。
让我们从第一个开始。没错,这不能仅用where
子句来表达。要表达这一点,需要添加帮助者特征
trait BorrowingFn<'a> {
type Fut: std::future::Future<Output = Something> + 'a;
fn call(self,arg: &'a Runtime) -> Self::Fut;
}
允许我们将边界写为
C: for<'a> BorrowingFn<'a>,
并为所有适用功能提供该特征的全面实现
impl<'a,Fu: 'a,F> BorrowingFn<'a> for F
where
F: FnOnce(&'a Runtime) -> Fu,Fu: std::future::Future<Output = ()> + 'a,{
type Fut = Fu;
fn call(self,rt: &'a Runtime) -> Fu {
self(rt)
}
}
好吧,所以它可以与异步功能一起使用,但是可以与需要类型推断的闭包一起使用吗?不幸的是,答案是“否”
error: implementation of `BorrowingFn` is not general enough
--> src/main.rs:33:5
|
5 | / trait BorrowingFn<'a> {
6 | | type Fut: std::future::Future<Output = ()> + 'a;
7 | | fn call(self,arg: &'a Runtime) -> Self::Fut;
8 | | }
| |_- trait `BorrowingFn` defined here
...
33 | start_with_runtime(|rt| async_main(rt)); // however,it does not work with closure type inference :-(
| ^^^^^^^^^^^^^^^^^^ implementation of `BorrowingFn` is not general enough
|
= note: `[closure@src/main.rs:33:24: 33:43]` must implement `BorrowingFn<'0>`,for any lifetime `'0`...
= note: ...but `[closure@src/main.rs:33:24: 33:43]` actually implements `BorrowingFn<'1>`,for some specific lifetime `'1`
这在rust-lang/rust#70263中被跟踪。编译器还不够聪明,还没有注意到此闭包需要更高级别的类型。
为了娱乐起见,我尝试在每夜使用-Z chalk
进行编译,但尚未为此做好准备(内部编译器错误)。
对不起,这是语言的限制。您只能在具体类型上指定生存期。一种解决方法是使用特征对象类型。
fn start_with_runtime<C,F,T>(closure: C)
where
C: for<'a> FnOnce(&'a Runtime) -> Pin<Box<dyn Future<Item = T> + Send + 'a>>,{
let rt = Runtime;
let _future = closure(&rt);
// block_on(future);
}