为什么带有.chars()的.flat_map()不能与std :: io :: Lines一起使用,而是使用字符串向量?

Ian*_*ott 6 iterator rust

我试图迭代stdin中的字符.该Read.chars()方法实现了这一目标,但不稳定.显而易见的替代方法是使用Read.lines()a 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());
}
Run Code Online (Sandbox Code Playgroud)

在Rust中的逐字符读取文件中提到,但它并没有真正解释原因.

我特别困惑的是它与文档中flat_map的示例有何不同,后者用于flat_map应用于.chars()字符串向量.我真的不明白这应该有什么不同.我看到的主要区别是我的代码也需要调用unwrap(),但是将最后一行更改为以下代码也不起作用:

let mut chars = lines.map(|x| x.unwrap());
let mut chars = chars.flat_map(|x| x.chars());
Run Code Online (Sandbox Code Playgroud)

它在第二行失败,所以问题似乎不是unwrap.

为什么最后一行不起作用,当文档中非常相似的行没有?有没有办法让这个工作?

She*_*ter 7

首先弄清楚闭包变量的类型是什么:

let mut chars = lines.flat_map(|x| {
    let () = x;
    x.unwrap().chars()
});
Run Code Online (Sandbox Code Playgroud)

这表明它是一个Result<String, io::Error>.后unwrap平它,这将是一个String.

接下来,看看str::chars:

fn chars(&self) -> Chars
Run Code Online (Sandbox Code Playgroud)

定义Chars:

pub struct Chars<'a> {
    // some fields omitted
}
Run Code Online (Sandbox Code Playgroud)

从那里,我们可以告诉调用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @ IanD.Scott虽然可以在`String`上调用`chars`,但请注意它需要`&self`(一个引用),它实际上是通过`Deref`实现的,这意味着实现实际上是在`str`上.因此`&self` =>`&str`. (2认同)
  • 感谢您使用let()= x;技巧确定变量类型! (2认同)