我试图迭代stdin中的字符。 Read.chars()
方法实现了这个目标,但是不稳定。显而易见的替代方法是使用Read.lines()
和flat_map
将其转换为字符迭代器。
这似乎应该工作,但不会,导致borrowed value does not live long enough
错误。
use std::io::BufRead;
fn main() {
let stdin = std::io::stdin();
let mut lines = stdin.lock().lines();
let mut chars = lines.flat_map(|x| x.unwrap().chars());
}
这在Read file character-by-character in Rust中提到过,但它并没有真正解释为什么。
我特别困惑的是它与documentation for flat_map
中的示例有何不同,后者使用flat_map
将.chars()
应用于字符串向量。我真的不明白这应该有什么不同。我看到的主要区别是我的代码也需要调用unwrap()
,但是将最后一行更改为以下代码也不起作用:
let mut chars = lines.map(|x| x.unwrap());
let mut chars = chars.flat_map(|x| x.chars());
它在第二行失败,所以问题似乎不是unwrap
。
为什么最后一行不起作用,当文档中非常相似的行没有?有没有办法让这个工作?
首先弄清楚闭包变量的类型是什么:
let mut chars = lines.flat_map(|x| {
let () = x;
x.unwrap().chars()
});
这表明它是一个Result<String, io::Error>
。在unwrap
ping之后,它将是一个String
。
接下来,看看str::chars
:
fn chars(&self) -> Chars
pub struct Chars<'a> {
// some fields omitted
}
从那里,我们可以告诉在字符串上调用chars
会返回一个迭代器,该迭代器具有对该字符串的引用。
每当我们有一个引用时,我们就知道引用不能比它借来的东西更长寿。在这种情况下,x.unwrap()
是所有者。接下来要检查的是所有权的结束。在这种情况下,闭包拥有String
,因此在闭包结束时,值将被删除并且任何引用都将失效。
除了代码试图返回仍然引用字符串的Chars
。哎呀。感谢Rust,代码没有错误!
与有效示例的区别在于所有权。在这种情况下,字符串由循环外部的向量拥有,并且在消耗迭代器之前不会丢弃它们。因此,没有终身问题。
这段代码真正想要的是into_chars
上的String
方法。迭代器可以获取值的所有权并返回字符。
不是最高效率,而是一个良好的开端:
struct IntoChars {
s: String,
offset: usize,
}
impl IntoChars {
fn new(s: String) -> Self {
IntoChars { s: s, offset: 0 }
}
}
impl Iterator for IntoChars {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
let remaining = &self.s[self.offset..];
match remaining.chars().next() {
Some(c) => {
self.offset += c.len_utf8();
Some(c)
}
None => None,
}
}
}
use std::io::BufRead;
fn main() {
let stdin = std::io::stdin();
let lines = stdin.lock().lines();
let chars = lines.flat_map(|x| IntoChars::new(x.unwrap()));
for c in chars {
println!("{}", c);
}
}
也可以看看: