Rust为何会对从未实际使用其值的类型的生命周期报错?
Rust为何会对从未实际使用其值的类型的生命周期报错?
嘿,我来给你把这个事儿掰扯明白!先把你给出的代码补全(毕竟你最后那个spawner_function没写完,我猜你就是在这个函数里尝试调用assert_spawnable时遇到的报错):
trait What { fn what(&self) -> impl Future<Output = ()> + Send; } trait Foo { type T: Send + Sync + What + 'static; } async fn generic_operation<C: Foo>(x: &C::T) { x.what().await; } struct FooStruct<C: Foo> { field: C::T, } pub fn assert_spawnable<F>(_: F) where F: Future + Send + 'static, F::Output: Send + 'static, {} async fn spawner_function<C: Foo>(x: FooStruct<C>) { // 就是写这行代码时编译器报错了对吧? assert_spawnable(async { generic_operation(&x.field).await; }); }
你肯定纳闷:明明Foo的关联类型T已经标了'static,我也没把这个引用存起来或者往外传,怎么编译器还揪着生命周期不放?甚至感觉自己根本没“实际使用”这个值的生命周期啊?
其实问题出在async块的捕获逻辑和'static约束的冲突上,而且Rust的编译器是吃“静态规则”这一套的,不是看你运行时实际有没有用到某个值的生命周期。
给你拆解一下:
- 首先,
assert_spawnable要求传入的Future必须是'static的——说白了就是这个future里不能带任何“活不过整个程序运行期”的引用。 - 你在async块里用了
&x.field,这个引用的生命周期是和参数x绑定的,也就是只在spawner_function的调用期间有效,显然不符合'static的要求。 - 再看
generic_operation,它是个async函数,编译器会把它转成一个匿名的Future类型,这个类型里会包含你传进去的&C::T引用,而这个引用的生命周期就和x绑死了。 - 当你把这个调用放进外层的async块时,外层async生成的Future就会包含内层的这个Future,自然也就带上了那个非
'static的引用。这时候编译器一查assert_spawnable的约束,直接就报错了——它不管你在函数里有没有“实际使用”这个引用的生命周期,只要代码结构上生成的类型不符合约束,就会拦下来。
那怎么解决呢?其实很简单,既然C::T本身已经是'static的了,我们可以把field的所有权转移到async块里,让引用的生命周期变成'static:
async fn spawner_function<C: Foo>(x: FooStruct<C>) { let field = x.field; // 把所有权拿出来 assert_spawnable(async move { // 用async move捕获所有权 generic_operation(&field).await; }); }
这样一来,async块拿到了field的所有权,而field是'static类型,所以整个外层Future就满足'static的要求了,编译器也就不会报错了。
总结一下:Rust的生命周期检查是静态的、基于代码结构和类型约束的,不是看你运行时实际有没有用到某个值的生命周期。哪怕你觉得自己没碰生命周期,只要代码生成的类型违反了约束,编译器就会及时给你提个醒。
内容来源于stack exchange
相关产品推荐
相关产品推荐

