< 返回版块

zhylmzr 发表于 2023-02-15 23:09

Tags:future,async,fn,lifetime

我想让结构体保存一个“接受可变引用参数,返回异步块”的函数指针方便我以后调用,但是总是在生命周期出现问题:

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`

评论区

写评论
作者 zhylmzr 2023-02-16 14:29

感谢详细的介绍,真的是太难了,活到老学到老

--
👇
苦瓜小仔: 噢,对了,一楼的设计更好,不仅更简洁,还拥有静态分发的优点,而且隐藏了生命周期。是首选。不过,都静态分发了,就无需 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,所以 很好理解,这无法编译

    let a = &false;
    let task = Task::new(ctx.clone(), |ctx| {
        Box::pin(async move {
            a;
            (*ctx).0 += 1;
            println!("{:?}", ctx);
            sleep(Duration::from_secs(1)).await;
        })
    });

而一楼的

impl<F> Task<F> 
where
    F: for<'a> Fn(&'a mut Context) -> Pin<Box<dyn Future<Output = ()> + 'a>>,

没有这个问题! F 是泛型,并且自身没有任何 lifetime bound,所以是 任意闭包,可以编译上面的情况

那么就只有这种写法吗?当然可以坚持最开始的写法(使用动态分发,尤其在静态分发很难做到的情况下,你的确需要动态分发),需要一些 trait 体操,来 构建更高阶的类型

struct Task<'f> {
    ctx:  Arc<Mutex<Context>>,
    func: Box<dyn Func<'f>>,
}

type BoxFut<'a> = Pin<Box<dyn Future<Output = ()> + 'a>>;
trait Func<'f>: 'f + for<'a> Fn(&'a mut Context) -> BoxFut<'a> {}
impl<'f, F: 'f + for<'a> Fn(&'a mut Context) -> BoxFut<'a>> Func<'f> for F {}

这里的 'f 与你的 'a 并不同,它代表闭包所捕获的生命周期。但它的功能有些痛点,这里就不讲了。

苦瓜小仔 2023-02-16 00:31

噢,对了,一楼的设计更好,不仅更简洁,还拥有静态分发的优点,而且隐藏了生命周期。是首选。不过,都静态分发了,就无需 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,所以 很好理解,这无法编译

    let a = &false;
    let task = Task::new(ctx.clone(), |ctx| {
        Box::pin(async move {
            a;
            (*ctx).0 += 1;
            println!("{:?}", ctx);
            sleep(Duration::from_secs(1)).await;
        })
    });

而一楼的

impl<F> Task<F> 
where
    F: for<'a> Fn(&'a mut Context) -> Pin<Box<dyn Future<Output = ()> + 'a>>,

没有这个问题! F 是泛型,并且自身没有任何 lifetime bound,所以是 任意闭包,可以编译上面的情况

那么就只有这种写法吗?当然可以坚持最开始的写法(使用动态分发,尤其在静态分发很难做到的情况下,你的确需要动态分发),需要一些 trait 体操,来 构建更高阶的类型

struct Task<'f> {
    ctx:  Arc<Mutex<Context>>,
    func: Box<dyn Func<'f>>,
}

type BoxFut<'a> = Pin<Box<dyn Future<Output = ()> + 'a>>;
trait Func<'f>: 'f + for<'a> Fn(&'a mut Context) -> BoxFut<'a> {}
impl<'f, F: 'f + for<'a> Fn(&'a mut Context) -> BoxFut<'a>> Func<'f> for F {}

这里的 'f 与你的 'a 并不同,它代表闭包所捕获的生命周期。但它的功能有些痛点,这里就不讲了。

作者 zhylmzr 2023-02-15 23:51

这也是个好方法,谢谢

--
👇
Grobycn: 换一个思路, 放宽结构体的限制,然后在 impl 块里面做约束。

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<F> {
    ctx: Arc<Mutex<Context>>,
    func: Box<F>,
}

impl<F> Task<F> 
where
    F: for<'a> Fn(&'a mut Context) -> Pin<Box<dyn Future<Output = ()> + 'a>>,
{
    fn new(ctx: Arc<Mutex<Context>>, func: F) -> Self
    {
        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);
作者 zhylmzr 2023-02-15 23:51

学到了,以为这个语法只能用在 where 里,原来也可以用在 trait object 中

--
👇
苦瓜小仔: 你需要 HRTB

struct Task {
    ctx:  Arc<Mutex<Context>>,
    func: Box<dyn for<'a> Fn(&'a mut Context) -> Pin<Box<dyn Future<Output = ()> + 'a>>>,
}

playground

苦瓜小仔 2023-02-15 23:34

你需要 HRTB

struct Task {
    ctx:  Arc<Mutex<Context>>,
    func: Box<dyn for<'a> Fn(&'a mut Context) -> Pin<Box<dyn Future<Output = ()> + 'a>>>,
}

playground

Grobycn 2023-02-15 23:33

换一个思路, 放宽结构体的限制,然后在 impl 块里面做约束。

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<F> {
    ctx: Arc<Mutex<Context>>,
    func: Box<F>,
}

impl<F> Task<F> 
where
    F: for<'a> Fn(&'a mut Context) -> Pin<Box<dyn Future<Output = ()> + 'a>>,
{
    fn new(ctx: Arc<Mutex<Context>>, func: F) -> Self
    {
        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);
1 共 6 条评论, 1 页