【问题标题】:How do I get a function to return a Vec of string-like things?如何获得一个函数来返回类似字符串的 Vec?
【发布时间】:2015-09-28 04:32:19
【问题描述】:

我有一大段代码可以打开文件并逐行搜索内容,然后对每个匹配的行执行一些操作。我想将其分解为它自己的函数,该函数获取文件的路径并为您提供匹配的行,但我不知道如何正确地分解它。

这是我认为很接近的内容,但出现编译器错误:

/// get matching lines from a path
fn matching_lines(p: PathBuf, pattern: &Regex) ->  Vec<String> {
    let mut buffer = String::new();
    // TODO: maybe move this side effect out, hand it a
    //       stream of lines or otherwise opened file
    let mut f = File::open(&p).unwrap();
    match f.read_to_string(&mut buffer) {
        Ok(yay_read) => yay_read,
        Err(_) => 0,
    };
    let m_lines: Vec<String> = buffer.lines()
        .filter(|&x| pattern.is_match(x)).collect();
    return m_lines;
}

还有编译器错误:

src/main.rs:109:43: 109:52 error: the trait `core::iter::FromIterator<&str>` is not implemented for the type `collections::vec::Vec<collections::string::String>` [E0277]
src/main.rs:109         .filter(|&x| pattern.is_match(x)).collect();
                                                          ^~~~~~~~~
src/main.rs:109:43: 109:52 help: run `rustc --explain E0277` to see a detailed explanation
src/main.rs:109:43: 109:52 note: a collection of type `collections::vec::Vec<collections::string::String>` cannot be built from an iterator over elements of type `&str`
src/main.rs:109         .filter(|&x| pattern.is_match(x)).collect();
                                                          ^~~~~~~~~
error: aborting due to previous error

如果我使用 String 而不是 &amp;str 我会收到此错误:

src/main.rs:108:30: 108:36 error: `buffer` does not live long enough
src/main.rs:108     let m_lines: Vec<&str> = buffer.lines()
                                             ^~~~~~

哪种有意义。我猜这些行会留在函数末尾超出范围的buffer 内,因此收集对字符串的引用向量并不能真正帮助我们。

如何返回一组行?

【问题讨论】:

    标签: rust borrow-checker


    【解决方案1】:

    让我们从这个版本开始,它在Rust Playground 上运行(提问时最好使用MCVE):

    use std::path::PathBuf;
    use std::fs::File;
    use std::io::Read;
    
    fn matching_lines(p: PathBuf, pattern: &str) -> Vec<String> {
        let mut buffer = String::new();
        let mut f = File::open(&p).unwrap();
        match f.read_to_string(&mut buffer) {
            Ok(yay_read) => yay_read,
            Err(_) => 0,
        };
        let m_lines: Vec<String> = buffer.lines()
            .filter(|&x| x.contains(pattern)).collect();
        return m_lines;
    }
    
    fn main() {
        let path = PathBuf::from("/etc/hosts");
        let lines = matching_lines(path, "local");    
    }
    

    让我们看看str::lines的签名:

    fn lines(&self) -> Lines // with lifetime elision
    fn lines<'a>(&'a self) -> Lines<'a> // without
    

    我首先展示了它在源代码中的样子,然后你可以在脑海中将它翻译成什么。它将返回一个字符串切片的迭代器,该迭代器由您读过的String 支持。这是一件好事,因为它非常有效,因为只需要进行一次分配。但是,您不能return an owned value and a reference to that value at the same time。正如Benjamin Lindley 建议的那样,最简单的做法是将每一行转换为一个拥有的字符串:

    let m_lines: Vec<String> =
        buffer
        .lines()
        .filter(|&x| x.contains(pattern))
        .map(ToOwned::to_owned)
        .collect();
    

    这样可以编译您的代码,但它仍然可以做得更好。您的match 语句可以替换为unwrap_or,但由于您完全忽略了错误情况,您还不如直接使用_

    let _ = f.read_to_string(&mut buffer);
    

    请注意,这真的不是一个好主意。报告错误很重要,当您最需要报告错误时,扔给错误会咬你!使用unwrap 可能更安全,并在发生错误时让程序死掉。

    接下来,不要使用显式的return 语句,也不要提供类型注释,除非你需要。由于您的函数返回Vec&lt;String&gt;,您可以将最后两行替换为:

    buffer
        .lines()
        .filter(|&x| x.contains(pattern))
        .map(ToOwned::to_owned)
        .collect()
    

    您还可以对您接受的 p 类型更加开放,以更好地匹配 File::open 支持的类型:

    fn matching_lines<P>(p: P, pattern: &str) -> Vec<String>
        where P: AsRef<Path>
    

    大家一起:

    use std::path::{Path, PathBuf};
    use std::fs::File;
    use std::io::Read;
    
    fn matching_lines<P>(p: P, pattern: &str) -> Vec<String>
        where P: AsRef<Path>
    {
        let mut buffer = String::new();
        let mut f = File::open(p).unwrap();
        let _ = f.read_to_string(&mut buffer);
    
        buffer
            .lines()
            .filter(|&x| x.contains(pattern))
            .map(ToOwned::to_owned)
            .collect()
    }
    
    fn main() {
        let path = PathBuf::from("/etc/hosts");
        let lines = matching_lines(path, "local");
        println!("{:?}", lines);
    }
    

    【讨论】:

    • 我真的很喜欢 unwrap 而不是让 _,在我从没想过会在他们这样做时发生的无声错误之后。
    • @SteveKlabnik 绝对!我只是在清理代码以更清楚地表明错误被忽略了。我会稍微调整一下文本以表明这绝对不是一个好主意。
    • 有没有办法可以只返回 Lines 而不是 Vec&lt;String&gt; ?我不断收到wrong number of lifetime parameters
    • @Conrad.Dean 不使用现在结构化的代码。那是other question I linked to。如果您将 String 的引用(通过 &amp;str)传递给返回 Lines 的方法,则可以执行类似的操作。
    【解决方案2】:

    您可以使用map 函数将字符串切片转换为拥有的String 对象。

    let m_lines: Vec<String> = buffer.lines()
            .filter(|&x| pattern.is_match(x))
            .map(|x| x.to_owned())
            .collect();
    

    然后您应该能够从函数中返回m_lines

    【讨论】:

      猜你喜欢
      • 2019-05-02
      • 2012-03-30
      • 2020-09-05
      • 2014-03-07
      • 2015-03-08
      • 2019-09-14
      • 2013-04-14
      • 1970-01-01
      相关资源
      最近更新 更多