Rust中如何使用同时捕获环境引用与接收参数引用的异步闭包
稳定版Rust解决方案
核心问题是你需要限制高阶生命周期'r满足外部环境生命周期'a比它更长,保证闭包捕获的环境变量在'r区间内始终有效。
Rust 1.59及以上稳定版本支持在高阶trait约束(HRTB)中添加where子句,直接实现你需要的生命周期限制,修改后的代码可以正常编译运行:
use std::pin::Pin; use std::future::Future; type BoxFuture<'a, Out> = Pin<Box<dyn Future<Output=Out> + 'a + Send>>; //////////////////////////////////////////////////////////////////////////////// // 3 -> 闭包同时使用内部参数和外部环境参数 // //////////////////////////////////////////////////////////////////////////////// /// 执行计算并校验输出与给定的expected值相等 fn run3<'a>(expected: &'a [u8]) -> impl Future<Output=()> + 'a { async move { let is_ok = compute_and_check3(|b: &[u8]| Box::pin(async move { b == expected // 同时使用输入参数和捕获的环境变量 })); let is_ok2 = is_ok.await; dbg!(is_ok2); } } /// 计算字节流(可包含复杂异步逻辑),随后使用用户提供的函数校验有效性,最后清理资源并返回结果 async fn compute_and_check3<'a, F>(check: F) -> bool where // 闭包本身的生命周期至少为'a(保证捕获的环境变量有效) F: 'a, // 对所有满足'a长于'r的生命周期'r,闭包都可以接收&'r [u8]并返回生命周期为'r的Future for<'r where 'a: 'r> FnOnce(&'r [u8]) -> BoxFuture<'r, bool> { let bytes = [0u8; 128]; let is_ok = check(&bytes).await; drop(bytes); is_ok }
原理说明
F: 'a约束保证闭包捕获的所有环境变量(这里就是传入的expected参数)的生命周期满足'a生命周期内始终有效。for<'r where 'a: 'r>是高阶生命周期的约束条件,明确要求所有可能的输入引用生命周期'r都短于外部环境生命周期'a,因此你可以安全的在返回的Future中同时使用输入引用&'r [u8]和环境引用&'a [u8],不会出现生命周期不匹配的问题。
这个方案完全兼容你之前的两种基础闭包用法:仅使用输入参数的闭包和仅捕获环境的闭包都可以正常传入这个版本的compute_and_check3函数使用。
低版本Rust兼容方案
如果你需要兼容1.59以下版本的Rust,可以通过定义双生命周期的BoxFuture类型别名实现相同效果:
type BoxFuture<'a, 'b, Out> = Pin<Box<dyn Future<Output=Out> + Send + 'a + 'b>>; async fn compute_and_check3<'a, F>(check: F) -> bool where for<'r> FnOnce(&'r [u8]) -> BoxFuture<'r, 'a, bool> { let bytes = [0u8; 128]; let is_ok = check(&bytes).await; drop(bytes); is_ok }
这种写法通过在类型别名的生命周期位置同时标注两个生命周期,表达Future需要同时满足输入引用生命周期'r和环境生命周期'a的约束,效果和上面的方案完全一致。
内容的提问来源于stack exchange,提问作者Demurgos
相关产品推荐
相关产品推荐

