6

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

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

/// 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而不是&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函数末尾超出范围的行内,因此收集对字符串的引用向量并没有真正帮助我们。

如何返回一组行?

4

2 回答 2

7

让我们从这个版本开始,它在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您阅读的内容支持。这是一件好事,因为它非常有效,因为只需要进行一次分配。但是,您不能同时返回拥有的值和对该值的引用正如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除非需要,否则不要使用显式语句,也不要提供类型注释。由于您的函数返回 a Vec<String>,您可以将最后两行替换为:

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);
}
于 2015-09-28T14:30:03.150 回答
6

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

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

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

于 2015-09-28T05:20:33.187 回答