Rust中含借用成员的结构体生命周期报错问题及求解
初始可运行代码
第一个例子中,结构体不包含引用字段,代码可正常编译:
struct SomeBigStructWithValues { values: Vec<isize>, other_big_crazy_stuff: usize, } impl SomeBigStructWithValues { fn merge(&mut self, other: &Self){ for (_i, val) in other.values.iter().enumerate() { self.values.push(val-0); } } } fn main() { let mut a = SomeBigStructWithValues { values: vec![0, 1], other_big_crazy_stuff: 1<<10 }; loop { let b = SomeBigStructWithValues { values: vec![2, 3, 4], other_big_crazy_stuff: 1<<10 }; a.merge(&b); break; } println!("{:?}", a.values); }
这段代码能通过检查,是因为merge仅复制b.values中的数值到a的向量里,不会让a持有任何指向b的引用。借用检查器能确认:只要merge执行期间b的引用有效即可,循环结束后b销毁完全不影响a后续的使用。
添加引用后的编译失败代码
当结构体加入Config引用字段后,代码编译报错:
#[derive(Clone)] struct Config{} const ConfigA: Config = Config{}; const ConfigB: Config = Config{}; struct SomeBigStructWithValues<'a> { globals: &'a Config, values: Vec<isize>, other_big_crazy_stuff: usize, } impl SomeBigStructWithValues<'_> { fn merge(&mut self, other: &Self){ for (_i, val) in other.values.iter().enumerate() { self.values.push(val-0); } } } fn main() { let mut a = SomeBigStructWithValues { globals: &ConfigA, values: vec![0, 1], other_big_crazy_stuff: 1<<10 }; loop { let localConfig = ConfigB.clone(); // code that changes localConfig let b = SomeBigStructWithValues { globals: &localConfig, values: vec![2, 3, 4], other_big_crazy_stuff: 1<<10 }; a.merge(&b); break; } println!("{:?}", a.values); }
编译错误信息:
error[E0597]: `localConfig` does not live long enough --> src/main.rs:26:52 | 24 | let localConfig = ConfigB.clone(); | ----------- binding `localConfig` declared here 25 | // code that changes localConfig 26 | let b = SomeBigStructWithValues { globals: &localConfig, values: vec![2, 3, 4], other_big_crazy_stuff: 1<<10 }; | ^^^^^^^^^^^^ borrowed value does not live long enough ... 29 | } | - `localConfig` dropped here while still borrowed 30 | println!("{:?}", a.values); | -------- borrow later used here
差异原因分析
第一个例子的结构体是完全拥有自身数据的类型,没有引用字段。借用检查器只需要保证merge执行时b的引用有效,无需关心b的生命周期是否覆盖a的整个生命周期。
第二个例子中,结构体带生命周期参数'a,表示globals引用的生命周期必须和结构体实例一致。当你写impl SomeBigStructWithValues<'_>时,编译器会自动推导为:
impl<'a> SomeBigStructWithValues<'a> { fn merge(&'a mut self, other: &'a Self) { // ... } }
这会把self(也就是a,生命周期覆盖整个main函数)和other(也就是b,生命周期受限于循环内的localConfig)的生命周期强行绑定。哪怕merge根本没用到globals字段,编译器也会基于结构体的生命周期约束进行检查,不会因为函数未使用某个字段就放宽规则。
解决方法:手动指定生命周期注解
你可以手动为merge函数指定不同的生命周期,解除self和other的绑定:
impl<'a, 'b> SomeBigStructWithValues<'a> { fn merge(&mut self, other: &'b Self) { for (_i, val) in other.values.iter().enumerate() { self.values.push(val - 0); } } }
这里'a是self的生命周期,'b是other的生命周期,两者无需一致。编译器现在能识别出merge只要求other在函数执行期间有效,不需要和self的生命周期匹配,代码即可正常编译。
更简洁的写法是使用匿名生命周期:
impl<'a> SomeBigStructWithValues<'a> { fn merge(&mut self, other: &Self<'_>) { // ... } }
&Self<'_>表示other可以是任意生命周期的SomeBigStructWithValues实例,无需和self的'a绑定。
内容的提问来源于stack exchange,提问作者David S.

