如何从字符串创建迭代器并存储到结构体中,解决Rust的所有权与生命周期问题?
这个问题其实是Rust的所有权系统在帮你踩刹车——你创建的Chars迭代器是依赖于input这个String的,而input在new函数结束时就被销毁了,迭代器自然就成了指向无效内存的悬垂引用,编译器当然会拦着你啦。
要解决这个问题,核心就是让迭代器依赖的String和迭代器本身一起存活,或者把迭代器改成拥有所有权的类型。这里有几种不同的方案,你可以根据自己的需求选:
方案1:把原String存在结构体里,用自引用(需要第三方库)
如果你想保留Chars迭代器的高效性,同时要记住迭代的位置,可以用ouroboros这个 crate 来创建自引用结构体。它帮你处理了Rust中自引用的安全问题:
首先在Cargo.toml里加依赖:
ouroboros = "0.17"
然后修改你的代码:
use ouroboros::self_referencing; use std::fs::File; use std::io::Read; #[self_referencing] pub struct CharStream { source: String, filename: String, #[borrows(source)] #[covariant] input: std::str::Chars<'this>, } impl CharStream { pub fn new(filename: String) -> Self { let mut file = File::open(&filename).unwrap(); let mut source = String::new(); // 这里最好处理一下错误,unwrap只是示例哦 file.read_to_string(&mut source).unwrap(); CharStreamBuilder { source, filename, input_builder: |source| source.chars(), }.build() } // 提供方法来获取下一个字符 pub fn next(&mut self) -> Option<char> { self.with_input_mut(|input| input.next()) } }
方案2:用所有权迭代器(无外部依赖)
如果可以接受把字符串转成Vec<char>的内存开销,那这个方案最简单直接——把字符都存到Vec里,然后用它的所有权迭代器,这样迭代器不依赖任何外部引用:
use std::fs::File; use std::io::Read; pub struct CharStream { input: std::vec::IntoIter<char>, filename: String, } impl CharStream { pub fn new(filename: String) -> Self { let mut file = File::open(&filename).unwrap(); let mut input_str = String::new(); file.read_to_string(&mut input_str).unwrap(); CharStream { input: input_str.chars().collect::<Vec<_>>().into_iter(), filename, } } pub fn next(&mut self) -> Option<char> { self.input.next() } }
方案3:装箱迭代器(动态分发,无外部依赖)
另一种不需要第三方库的方法是把迭代器装箱成Box<dyn Iterator<Item = char>>,这样结构体拥有迭代器的所有权,摆脱了生命周期的束缚,代价是一点点动态分发的运行时开销:
use std::fs::File; use std::io::Read; use std::iter::Iterator; pub struct CharStream { input: Box<dyn Iterator<Item = char>>, filename: String, } impl CharStream { pub fn new(filename: String) -> Self { let mut file = File::open(&filename).unwrap(); let mut input_str = String::new(); file.read_to_string(&mut input_str).unwrap(); CharStream { input: Box::new(input_str.chars()), filename, } } pub fn next(&mut self) -> Option<char> { self.input.next() } }
方案4:延迟生成迭代器(最简洁,适合按需迭代)
如果你不需要记住迭代的位置,只是每次需要的时候从头开始遍历,那直接把原字符串存在结构体里,每次调用chars()生成新迭代器就好,这是最符合Rust风格的方案:
use std::fs::File; use std::io::Read; pub struct CharStream { source: String, filename: String, } impl CharStream { pub fn new(filename: String) -> Self { let mut file = File::open(&filename).unwrap(); let mut source = String::new(); file.read_to_string(&mut source).unwrap(); CharStream { source, filename } } pub fn chars(&self) -> std::str::Chars<'_> { self.source.chars() } }
选哪个?
- 要保留迭代位置且追求性能:方案1或方案3
- 想简单粗暴无依赖:方案2
- 不需要保留迭代位置:方案4绝对是最优解
内容的提问来源于stack exchange,提问作者aryndin
相关产品推荐
相关产品推荐

