Rust中外层函数的Future内使用可变引用的编译问题
Rust异步闭包捕获可变引用的生命周期问题
问题背景
我们可以通过以下代码原地修改函数参数:
let mut foo = 1; let mut fun: Box<dyn FnMut(&mut i32) -> _> = Box::new(|f| { *f += 1; }); fun(&mut foo); assert_eq!(foo, 2);
但当需要让函数返回Future,在await时修改参数时,编写的代码出现编译错误:
#[tokio::main] async fn main() { let mut foo = 1; assert_eq!(foo, 1); let mut fun: Box<dyn FnMut(&mut i32) -> _> = Box::new(|f| { async move { *f += 1; } }); fun(&mut foo).await; assert_eq!(foo, 2); }
编译错误信息:
error: lifetime may not live long enough --> src/main.rs:7:9 | 6 | let mut fun: Box<dyn FnMut(&mut i32) -> _> = Box::new(|f| { | -- return type of closure `impl Future<Output = ()>` contains a lifetime `'2` | | | has type `&'1 mut i32` 7 | / async move { 8 | | *f += 1; 9 | | } | |_________^ returning this value requires that `'1` must outlive `'2` error: could not compile `playground` due to previous error
尝试使用Box<dyn FnMut(&'static mut i32) -> _>后,又出现foo生命周期不足的问题,请问如何解决?
问题原因
异步闭包返回的Future会捕获&mut i32,但Rust无法自动推断出该引用的生命周期能覆盖Future的整个生命周期。当把闭包装入Box<dyn FnMut(...)> trait对象时,必须明确生命周期约束,否则编译器无法保证引用在Future被await时仍然有效。
解决方案
方案一:使用高阶生命周期(HRTB)标注闭包类型
通过for<'a>语法(高阶生命周期)明确参数引用的生命周期与返回Future的生命周期绑定:
use std::future::Future; #[tokio::main] async fn main() { let mut foo = 1; assert_eq!(foo, 1); // 用for<'a>声明闭包可接受任意生命周期的&mut i32,且返回的Future与该引用同生命周期 let mut fun: Box<dyn for<'a> FnMut(&'a mut i32) -> impl Future<Output = ()> + 'a> = Box::new(|f| { async move { *f += 1; } }); fun(&mut foo).await; assert_eq!(foo, 2); }
方案二:装箱Future为Pin<Box<dyn Future<...>>>
如果需要更灵活的Future类型(比如不同闭包返回不同Future),可以将Future装箱,同时绑定生命周期:
use std::future::Future; use std::pin::Pin; #[tokio::main] async fn main() { let mut foo = 1; assert_eq!(foo, 1); let mut fun: Box<dyn for<'a> FnMut(&'a mut i32) -> Pin<Box<dyn Future<Output = ()> + 'a>>> = Box::new(|f| { Box::pin(async move { *f += 1; }) }); fun(&mut foo).await; assert_eq!(foo, 2); }
为什么'static不可行?
当指定&'static mut i32时,要求引用必须存活至程序结束,但foo是栈上局部变量,生命周期仅局限于main函数内部,无法满足'static的严格要求,因此会触发生命周期不足的错误。
内容的提问来源于stack exchange,提问作者Kevin
相关产品推荐
相关产品推荐

