Con*_*ean 2 rust borrow-checker
我有一大堆代码打开文件并逐行搜索内容,然后对每个匹配的行执行某些操作.我想把它考虑到它自己的函数中,它接受一个文件的路径并给你匹配的行,但我无法弄清楚如何正确地考虑这个.
这是我认为很接近的,但是我得到了一个编译器错误:
/// 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;
}
Run Code Online (Sandbox Code Playgroud)
而编译错误:
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
Run Code Online (Sandbox Code Playgroud)
如果我用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()
^~~~~~
Run Code Online (Sandbox Code Playgroud)
哪种有道理.我猜这些行保留在buffer
函数末尾的范围之外,因此收集字符串引用的向量并不能真正帮助我们.
如何返回一系列行?
您可以String
使用该map
函数将字符串切片转换为拥有的对象.
let m_lines: Vec<String> = buffer.lines()
.filter(|&x| pattern.is_match(x))
.map(|x| x.to_owned())
.collect();
Run Code Online (Sandbox Code Playgroud)
然后,您应该能够m_lines
从该功能返回.
让我们从这个版本开始,该版本在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");
}
Run Code Online (Sandbox Code Playgroud)
让我们来看看签名str::lines
:
fn lines(&self) -> Lines // with lifetime elision
fn lines<'a>(&'a self) -> Lines<'a> // without
Run Code Online (Sandbox Code Playgroud)
我首先在源代码中展示了它的样子,以及你可以在心理上把它翻译成第二个样子.它将返回由String
您读过的字符串切片的迭代器.这是一件好事,因为它非常有效,因为只需要进行一次分配.但是,您不能同时返回拥有的值和对该值的引用.最简单的做法是将每一行转换为一个拥有的字符串,正如Benjamin Lindley建议的那样:
let m_lines: Vec<String> =
buffer
.lines()
.filter(|&x| x.contains(pattern))
.map(ToOwned::to_owned)
.collect();
Run Code Online (Sandbox Code Playgroud)
这会让你的代码编译,但它仍然可以更好.您的match
语句可以替换为unwrap_or
,但由于您完全忽略了错误情况,您可以使用_
:
let _ = f.read_to_string(&mut buffer);
Run Code Online (Sandbox Code Playgroud)
请注意,这确实不是一个好主意.错误对于报告很重要,如果您需要报告错误,丢失错误会咬你!使用它可能更安全,unwrap
并在发生错误时让程序死掉.
接下来,return
除非您需要,否则不要使用显式语句,也不要提供类型注释.由于你的函数返回a Vec<String>
,你可以用以下代码替换最后两行:
buffer
.lines()
.filter(|&x| x.contains(pattern))
.map(ToOwned::to_owned)
.collect()
Run Code Online (Sandbox Code Playgroud)
您也可以对您接受的类型更加开放p
,以便更好地匹配File::open
支持:
fn matching_lines<P>(p: P, pattern: &str) -> Vec<String>
where P: AsRef<Path>
Run Code Online (Sandbox Code Playgroud)
全部一起:
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);
}
Run Code Online (Sandbox Code Playgroud)