使用RAII风格守卫实现Rust词法分析器游标时遇生命周期错误
解决RAII游标守卫的Rust生命周期错误
问题背景
编写词法分析器时,希望通过RAII实现字符串游标前进/回退功能,使用CharStreamGuard作为游标索引类型:
- 可通过
new创建顶层实例 - 可通过
extend从父守卫扩展出子守卫
但编译器抛出以下生命周期错误:
error[E0597]: `csg` does not live long enough --> src/lib.rs:78:48 | 76 | let mut csg = CharStreamGuard::new(&s); | ------- binding `csg` declared here 77 | { 78 | let mut csg2 = CharStreamGuard::extend(&mut csg); | ^^^^^^^^ borrowed value does not live long enough ... 82 | } | - | | | `csg` dropped here while still borrowed | borrow might be used here, when `csg` is dropped and runs the `Drop` code for type `CharStreamGuard` error[E0499]: cannot borrow `csg` as mutable more than once at a time --> src/lib.rs:81:5 | 78 | let mut csg2 = CharStreamGuard::extend(&mut csg); | -------- first mutable borrow occurs here ... 81 | csg.accept(); | ^^^ | | | second mutable borrow occurs here | first borrow later used here
原实现代码:
#[derive(PartialEq)] enum GuardStatus { Unset, Accepted, Rejected, } pub struct CharStreamGuard<'a, 'b> { src: &'a String, parent: Option<&'b mut CharStreamGuard<'a, 'b>>, index: usize, status: GuardStatus } impl<'a,'b> CharStreamGuard<'a,'b> { fn new(src: &'a String) -> CharStreamGuard<'a,'b> { return CharStreamGuard { src: src, parent: None, index: 0, status: GuardStatus::Unset }; } fn extend<'c>(parent: &'c mut CharStreamGuard<'a,'b>) -> CharStreamGuard<'a,'c> where 'c : 'b { let index = parent.index; return CharStreamGuard { src: parent.src, parent: Some(parent), index: index, status: GuardStatus::Unset }; } fn accept(&mut self) { self.status = GuardStatus::Accepted; if let Some(parent) = self.parent.as_mut() { parent.index = self.index; } } fn reject(&mut self) { self.status = GuardStatus::Rejected; } } impl Drop for CharStreamGuard<'_,'_> { fn drop(&mut self) -> () { if self.status == GuardStatus::Unset { panic!("guard was neither accpeted nor rejected!"); } } } fn test_it() { let s = String::new(); let mut csg = CharStreamGuard::new(&s); { let mut csg2 = CharStreamGuard::extend(&mut csg); csg2.accept(); } csg.accept(); }
错误根源
原代码的核心问题是生命周期参数设计错误:
- 结构体
CharStreamGuard<'a, 'b>中,parent字段的类型是&'b mut CharStreamGuard<'a, 'b>,形成了循环的生命周期约束 extend方法中'c: 'b的约束进一步混淆了编译器对借用范围的判断,导致它认为父守卫的借用会持续到自身被drop的时刻,同时无法识别子守卫作用域结束后借用已经释放
解决方案
重新设计结构体的生命周期,去掉冗余的循环约束,让子守卫的生命周期明确短于父守卫:
修改后的完整代码
#[derive(PartialEq)] enum GuardStatus { Unset, Accepted, Rejected, } pub struct CharStreamGuard<'a> { src: &'a String, // 父守卫的引用生命周期独立于当前守卫的src生命周期 parent: Option<&'a mut CharStreamGuard<'a>>, index: usize, status: GuardStatus, } impl<'a> CharStreamGuard<'a> { fn new(src: &'a String) -> Self { CharStreamGuard { src, parent: None, index: 0, status: GuardStatus::Unset, } } // 子守卫持有父守卫的可变引用,其生命周期与父守卫的借用周期绑定 fn extend<'b>(parent: &'b mut CharStreamGuard<'a>) -> CharStreamGuard<'b> { let index = parent.index; CharStreamGuard { src: parent.src, parent: Some(parent), index, status: GuardStatus::Unset, } } fn accept(&mut self) { self.status = GuardStatus::Accepted; if let Some(parent) = self.parent.as_mut() { parent.index = self.index; } } fn reject(&mut self) { self.status = GuardStatus::Rejected; } } impl Drop for CharStreamGuard<'_> { fn drop(&mut self) { if self.status == GuardStatus::Unset { panic!("guard was neither accepted nor rejected!"); } } } fn test_it() { let s = String::new(); let mut csg = CharStreamGuard::new(&s); { let mut csg2 = CharStreamGuard::extend(&mut csg); csg2.accept(); } // 子守卫作用域结束,释放对父守卫的可变借用 csg.accept(); // 父守卫可正常被借用 }
关键修改点
- 简化结构体生命周期:将
CharStreamGuard的生命周期参数从两个简化为一个'a,仅绑定字符串源的生命周期 - 明确子守卫生命周期:
extend方法中,子守卫的生命周期'b与父守卫的借用周期绑定,确保子守卫的生命周期短于父守卫 - 修正Drop实现:去掉冗余的生命周期占位符,使用
'_简化实现
这样修改后,编译器可以正确识别借用的范围,子守卫作用域结束后父守卫的借用会被释放,两个错误都会被解决。
内容的提问来源于stack exchange,提问作者user1055947
相关产品推荐
相关产品推荐

