【问题标题】:Why does .flat_map() with .chars() not work with std::io::Lines, but does with a vector of Strings?为什么带有 .chars() 的 .flat_map() 不适用于 std::io::Lines,但适用于字符串向量?
【发布时间】:2017-03-14 03:28:17
【问题描述】:

我正在尝试迭代标准输入中的字符。 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

当文档中非常相似的行不起作用时,为什么最后一行不起作用?有什么办法可以让它工作吗?

【问题讨论】:

    标签: iterator rust


    【解决方案1】:

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

    let mut chars = lines.flat_map(|x| {
        let () = x;
        x.unwrap().chars()
    });
    

    这表明它是Result<String, io::Error>。在unwrapping 之后,它将是一个String

    接下来看str::chars

    fn chars(&self) -> Chars
    

    还有definition of Chars

    pub struct Chars<'a> {
        // some fields omitted
    }
    

    由此,我们可以看出,对字符串调用 chars 会返回一个迭代器,该迭代器具有对字符串的引用

    只要我们有一个引用,我们就知道该引用不能比借用它的东西更长寿。在这种情况下,x.unwrap() 是所有者。接下来要检查的是所有权在哪里结束。在这种情况下,闭包拥有String,因此在闭包结束时,该值被删除并且所有引用都无效。

    除了代码试图返回仍然引用字符串的Chars。哎呀。感谢 Rust,代码没有段错误!

    与有效示例的区别在于所有权。在这种情况下,字符串由循环外的向量拥有,并且在迭代器被消耗之前它们不会被删除。因此不存在生命周期问题。

    这段代码真正想要的是String 上的into_chars 方法。该迭代器可以获取值的所有权并返回字符。


    不是最高效率,而是一个好的开始:

    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);
        }
    }
    

    另见:

    【讨论】:

    • 啊,我明白了,谢谢!我想我感到困惑的是函数都处理String而不是&amp;str,所以它似乎应该移动这些值。但情况并非如此,因为闭包不返回实际值,而是稍后延迟评估的迭代器,并且这些迭代器包含对原始对象的引用。
    • @IanD.Scott 虽然chars 可以在String 上调用,但请注意它需要&amp;self(一个引用)并且它实际上是通过Deref 实现的,这意味着实现实际上是在str。因此&amp;self => &amp;str.
    • 感谢let () = x; 确定变量类型的技巧!
    猜你喜欢
    • 2021-11-06
    • 2017-01-06
    • 1970-01-01
    • 2019-03-06
    • 2014-09-20
    • 2013-12-17
    • 2014-01-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多