Rust中for循环产出引用的生命周期注解问题及修复咨询
Rust生命周期错误分析与修复
问题重现
可运行的原始代码
struct Lexer<'a> { text: String, words: Vec<(usize, &'a str)>, } impl<'a> Lexer<'a> { fn from_str(text: String) -> Lexer<'a> { Lexer { text, words: Vec::new(), } } } fn main() { let mut lexer = Lexer::from_str(String::from("Mary had a little lamb")); for (count, word) in lexer.text.split(' ').enumerate() { lexer.words.push((count-1, word)); } println!("{}, {:?}", lexer.text, lexer.words); }
修改后出现错误的代码
将main中的循环逻辑提取到lex方法后:
impl<'a> Lexer<'a> { fn lex(&mut self) { for (count, word) in self.text.split(' ').enumerate() { self.words.push((count-1, word)); } } }
错误信息
error: lifetime may not live long enough --> src/main.rs:27:13 | 24 | impl<'a> Lexer<'a> { | -- lifetime `'a` defined here 25 | fn lex(&mut self) { | - let's call the lifetime of this reference `'1` 26 | for (count, word) in self.text.split(' ').enumerate() { 27 | self.words.push((count-1, word)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'1` must outlive `'a`
原因解析
为什么main函数中无错误?
在main函数内,编译器可以直接完成生命周期推断:
lexer是拥有所有权的变量,生命周期覆盖整个main函数word是从lexer.text分割出的切片,其生命周期与lexer.text完全绑定- 结构体
Lexer的生命周期'a被自动推断为与lexer的生命周期一致,因此word的生命周期满足words向量的要求,不会触发错误。
为什么移到方法后报错?
当逻辑进入lex方法时:
- 方法的
&mut self拥有临时生命周期'1(仅覆盖方法调用的时间段) word作为self.text的切片,生命周期绑定到'1- 但结构体的
words向量要求存储的切片生命周期为'a(结构体本身的生命周期),编译器无法保证'1的长度能覆盖'a——你试图把一个仅在方法调用期间有效的切片,存储到生命周期更长的结构体成员中,这违背了Rust的内存安全规则,因此报错。
修复方案
方案1:存储字符串所有权而非引用
这是最简单的修复方式,将words的类型改为存储字符串所有权,彻底摆脱生命周期依赖:
struct Lexer { text: String, words: Vec<(usize, String)>, } impl Lexer { fn from_str(text: String) -> Lexer { Lexer { text, words: Vec::new(), } } fn lex(&mut self) { for (count, word) in self.text.split(' ').enumerate() { self.words.push((count-1, word.to_string())); } } } fn main() { let mut lexer = Lexer::from_str(String::from("Mary had a little lamb")); lexer.lex(); println!("{}, {:?}", lexer.text, lexer.words); }
方案2:调整结构体,持有外部字符串引用
如果确实需要存储切片而非所有权,可以让结构体持有外部字符串的引用,将生命周期'a绑定到外部字符串:
struct Lexer<'a> { text: &'a str, words: Vec<(usize, &'a str)>, } impl<'a> Lexer<'a> { fn from_str(text: &'a str) -> Lexer<'a> { Lexer { text, words: Vec::new(), } } fn lex(&mut self) { for (count, word) in self.text.split(' ').enumerate() { self.words.push((count-1, word)); } } } fn main() { let text = String::from("Mary had a little lamb"); let mut lexer = Lexer::from_str(&text); lexer.lex(); println!("{}, {:?}", lexer.text, lexer.words); }
方案3:自引用结构体(高级,不推荐初学者)
如果必须让结构体同时持有自身的String和对应切片,可以通过Pin和unsafe实现自引用,但需要手动保证内存安全:
use std::pin::Pin; use std::marker::PhantomPinned; struct Lexer { text: String, words: Vec<(usize, &'static str)>, _pin: PhantomPinned, } impl Lexer { fn from_str(text: String) -> Pin<Box<Self>> { let mut boxed = Box::pin(Lexer { text, words: Vec::new(), _pin: PhantomPinned, }); let text_ptr = boxed.text.as_str() as *const str; unsafe { let mut_ref = Pin::as_mut(&mut boxed); for (count, word) in (*text_ptr).split(' ').enumerate() { let static_word = std::mem::transmute::<&str, &'static str>(word); mut_ref.words.push((count-1, static_word)); } } boxed } } fn main() { let mut lexer = Lexer::from_str(String::from("Mary had a little lamb")); println!("{}, {:?}", lexer.text, lexer.words); }
内容的提问来源于stack exchange,提问作者lagarkane
相关产品推荐
相关产品推荐

