我想让结构体保存一个“接受可变引用参数,返回异步块”的函数指针方便我以后调用,但是总是在生命周期出现问题:
use std::{borrow::BorrowMut, future::Future, pin::Pin, sync::Arc, time::Duration};
use tokio::{sync::Mutex, time::sleep};
#[tokio::main]
async fn main() {
let ctx = Arc::new(Mutex::new(Context(0)));
let task = Task::new(ctx.clone(), |ctx| {
Box::pin(async move {
(*ctx).0 += 1;
println!("{:?}", ctx);
sleep(Duration::from_secs(1)).await;
})
});
task.run().await;
}
struct Task<'a> {
ctx: Arc<Mutex<Context>>,
func: Box<dyn Fn(&'a mut Context) -> Pin<Box<dyn Future<Output = ()> + 'a>>>,
}
impl<'a> Task<'a> {
fn new<T>(ctx: Arc<Mutex<Context>>, func: T) -> Self
where
T: Fn(&'a mut Context) -> Pin<Box<dyn Future<Output = ()> + 'a>>,
{
Self {
ctx,
func: Box::new(func),
}
}
async fn run(&self) {
loop {
(self.func)(self.ctx.lock().await.borrow_mut()).await; // 这里报错, `self`生命周期是`‘1`, 它比`.func`的 `'a` 生命周期更短
}
}
}
#[derive(Debug)]
struct Context(i32);
详细的输出信息:
error[E0716]: temporary value dropped while borrowed
--> src\main.rs:36:25
|
34 | async fn run(&self) {
| ----- lifetime `'1` appears in the type of `self`
35 | loop {
36 | (self.func)(self.ctx.lock().await.borrow_mut()).await;
| ------------^^^^^^^^^^^^^^^^^^^^^-------------- - temporary value is freed at the end of this statement
| | |
| | creates a temporary which is freed while still in use
| argument requires that borrow lasts for `'1`
error: lifetime may not live long enough
--> src\main.rs:36:13
|
23 | impl<'a> Task<'a> {
| -- lifetime `'a` defined here
...
34 | async fn run(&self) {
| - let's call the lifetime of this reference `'1`
35 | loop {
36 | (self.func)(self.ctx.lock().await.borrow_mut()).await;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'1` must outlive `'a`
1
共 6 条评论, 1 页
评论区
写评论感谢详细的介绍,真的是太难了,活到老学到老
--
👇
苦瓜小仔: 噢,对了,一楼的设计更好,不仅更简洁,还拥有静态分发的优点,而且隐藏了生命周期。是首选。不过,都静态分发了,就无需
Box<F>
我解释第三个优点吧:
func: Box<dyn for<'a> Fn(&'a mut Context) -> Pin<Box<dyn Future<Output = ()> + 'a>>>
是func: Box<dyn for<'a> Fn(&'a mut Context) -> Pin<Box<dyn Future<Output = ()> + 'a>> + 'static>
的简写。如果你还不知道这个 lifetime elision rule 的话,点这里:trait object 有自己的规则。这个 'static 与 Fn 并列,说明这个闭包所捕获的类型必须满足 'static,所以 很好理解,这无法编译。
而一楼的
没有这个问题! F 是泛型,并且自身没有任何 lifetime bound,所以是 任意闭包,可以编译上面的情况。
那么就只有这种写法吗?当然可以坚持最开始的写法(使用动态分发,尤其在静态分发很难做到的情况下,你的确需要动态分发),需要一些 trait 体操,来 构建更高阶的类型:
这里的 'f 与你的 'a 并不同,它代表闭包所捕获的生命周期。但它的功能有些痛点,这里就不讲了。
噢,对了,一楼的设计更好,不仅更简洁,还拥有静态分发的优点,而且隐藏了生命周期。是首选。不过,都静态分发了,就无需
Box<F>
我解释第三个优点吧:
func: Box<dyn for<'a> Fn(&'a mut Context) -> Pin<Box<dyn Future<Output = ()> + 'a>>>
是func: Box<dyn for<'a> Fn(&'a mut Context) -> Pin<Box<dyn Future<Output = ()> + 'a>> + 'static>
的简写。如果你还不知道这个 lifetime elision rule 的话,点这里:trait object 有自己的规则。这个 'static 与 Fn 并列,说明这个闭包所捕获的类型必须满足 'static,所以 很好理解,这无法编译。
而一楼的
没有这个问题! F 是泛型,并且自身没有任何 lifetime bound,所以是 任意闭包,可以编译上面的情况。
那么就只有这种写法吗?当然可以坚持最开始的写法(使用动态分发,尤其在静态分发很难做到的情况下,你的确需要动态分发),需要一些 trait 体操,来 构建更高阶的类型:
这里的 'f 与你的 'a 并不同,它代表闭包所捕获的生命周期。但它的功能有些痛点,这里就不讲了。
这也是个好方法,谢谢
--
👇
Grobycn: 换一个思路, 放宽结构体的限制,然后在 impl 块里面做约束。
学到了,以为这个语法只能用在 where 里,原来也可以用在 trait object 中
--
👇
苦瓜小仔: 你需要 HRTB
playground
你需要 HRTB
playground
换一个思路, 放宽结构体的限制,然后在 impl 块里面做约束。