Rust闭包可变借用参数生命周期如何处理?

想在闭包参数里传可变借用,需要如何解决生命周期问题?
rustc 1.65.0

296 |       do_demo(|demo| async move {
    |  ______________-----_^
    | |              |   |
    | |              |   return type of closure `impl Future<Output = String>` contains a lifetime `'2`
    | |              has type `&'1 mut Demo`
297 | |         println!("demo: {:?}", demo);
298 | |         demo.print_mut();
299 | |         format!("test demo, id: {}", demo.id)
300 | |     })
    | |_____^ returning this value requires that `'1` must outlive `'2`
use std::future::Future;

#[derive(Debug)]
struct Demo {
    id: i32,
    name: String,
}

impl Demo {
    fn print(self) {
        println!("id:{}, name:{:?}", self.id, self.name);
    }
    fn print_mut(&mut self) {
        println!("id:{}, name:{:?}", self.id, self.name);
    }
}

async fn do_demo<F, U>(mut f: F) -> Result<(), String>
where
    F: for<'a> FnMut(&'a Demo) -> U,
    U: Future<Output = String>,
{
    let demo = Demo {
        id: 1,
        name: "test".to_string(),
    };

    let ret = f(&demo).await;

    demo.print();

    println!("f() ret: {:?}", ret);

    Ok(())
}

#[tokio::test]
async fn demo() {
    do_demo(|demo| async move {
        println!("demo: {:?}", demo);
        demo.print_mut();
        format!("test demo, id: {}", demo.id)
    })
    .await
    .unwrap();
}
阅读 2.9k
2 个回答

在rust playground中你的代码并不会报错的;感觉是tokio::test 并不会被rust playground 识别, 然后我添加了 tokio::mian.......

其实我也郁闷为什么会有这样的错误,因为在de_demo 中 f(&demo).await 完成后demo 并没有drop

当看到这样的提示:returning this value requires that '1 must outlive '2;又看到demo和f(&demo)都在de_demo里面; 我的想法是把 demo 放到do_demo函数的外面试试

async fn do_demo<F,U>(f: F) -> Result<(), String>
where
    F: Fn() -> U,
    U: Future<Output = String>,
{
    let ret = f().await;
    //demo.print();
    println!("f() ret: {:?}", ret);
    
    Ok(())
}

#[tokio::main]
async fn main(){
  
    let demo = Demo {
        id: 1,
        name: "test".to_string(),
    };
    
    do_demo( ||async  {
        println!("demo: {:?}", demo);
        //demo.print_mut();//cannot borrow `demo` as mutable, as it is not declared as mutable
        format!("test demo, id: {}", demo.id)
       
    })
    .await
    .unwrap();
}

当然也可以这样,就是把demo移到闭包中:

async fn do_demo<F,U>(mut f: F) -> Result<(), String>
where
    F:  FnMut(Demo) -> U,
    U: Future<Output = String>,
{
   
    let demo = Demo {
        id: 1,
        name: "test".to_string(),
    };
    
    let ret = f(demo).await;

    //demo.print();
    println!("f() ret: {:?}", ret);

    Ok(())
}

#[tokio::main]
async fn main(){
  
    do_demo(|demo| async move {
        println!("demo: {:?}", demo);
        //demo.print_mut();//cannot borrow `demo` as mutable, as it is not declared as mutable
        format!("test demo, id: {}", demo.id)
       
    })
    .await
    .unwrap();
}

其他失败的尝试: 给demo 添加static 生命周期; 因为让static告诉编译器demo这个参数的生命周期你要有多长就有多长请放心使用,可是失败了

 F: FnMut(& 'static Demo) -> U,

有时候遇到问题就采用迂回战术(换一种写法)满足功能的同时也能通过编译;可有时候怎么能甘心尼。。。。

其他

对 F:for<'a> FnMut(&'a Demo) -> U 这种生命周期标注很好奇;删除for<'a> 得到的下面的提示就释然了

   |
23 |     F: FnMut(&'a Demo) -> U,
   |               ^^ undeclared lifetime
   |
   = note: for more information on higher-ranked polymorphism, visit https://doc.rust-lang.org/nomicon/hrtb.html
help: consider making the bound lifetime-generic with a new `'a` lifetime
   |
23 |     F: for<'a> FnMut(&'a Demo) -> U,
   |        +++++++
help: consider making the bound lifetime-generic with a new `'a` lifetime
   |
23 |     for<'a> F: FnMut(&'a Demo) -> U,
   |     +++++++
help: consider introducing lifetime `'a` here
   |
21 | async fn do_demo<'a, F, U>(mut f: F) -> Result<(), String>

unsafe block

我专门创建了 demo 项目来了运行题目中的示例代码,此处的 lifetime 是这样的:

  1. 传递到 do_demo 函数接收的闭包的 demo 参数是作为引用传参 demo: &Demo
  2. do_demo 接收的是 async 闭包,结合 do_demo 函数的定义,实际上 F: for<'a> FnMut(&'a Demo) -> U, 参数里面隐含了 U 不可超过 F 闭包的lifetime,因为 F 要求对任意生命周期 for<'a> 成立; 而 Future<Output = String> 隐含了 'static lifetime, 因为它是异步的闭包,所有异步闭包都必须是编译时就静态生成。

2022年12月8日更新

补充参考资料来源 tokio tutorials - spawning

When you spawn a task on the Tokio runtime, its type's lifetime must be 'static. This means that the spawned task must not contain any references to data owned outside the task.

故不应该在传递参数到闭包的时候传递指向所有权超出闭包外的数据。