我对Rust很新。在尝试小事时,我编写了以下代码。它只扫描特定字符串(“已启动”)的文件(作为参数给出)并打印出匹配的行:
use std::os;
use std::io::BufferedReader;
use std::io::File;
fn main() {
for target in os::args().iter() {
scan_file(target);
}
}
fn scan_file(path_str: &String) {
let path = Path::new(path_str.as_bytes());
let file = File::open(&path);
let mut reader = BufferedReader::new(file);
for line in reader.lines() {
match line {
Ok(s) => {
if s.as_slice().contains("Started ") {
print!("{}", s);
}
}
Err(_) => return,
}
}
}
我的问题是:如何重构函数scan_file
以使它看起来像这样(或类似的)?:
fn scan_file(path_str: &String) {
for line in each_line_in_file_with_path(path_str) {
match line {
Ok(s) => {
if s.as_slice().contains("Started ") {
print!("{}", s);
}
}
Err(_) => return,
}
}
}
在这个新版本的函数中,三个变量声明都消失了。相反,函数each_line_in_file_with_path
应该处理所有“将路径转换为行”,返回迭代器。
我已经尝试了许多不成功的事情,总是由于变量超出范围而对我的需求太早。我理解我遇到的问题(我认为),但无法找到解释如何处理的问题。
答案 0 :(得分:3)
不可能实现一个有效的each_line_in_file_with_path
函数 - 至少,不是没有添加一些开销和不安全的代码。
让我们看看所涉及的价值及其类型。首先是path
,类型为Path
(posix::Path
或windows::Path
)。这些类型的构造函数按值获得BytesContainer
,因此它们拥有它的所有权。这里没有问题。
下一个是file
,类型为IoResult<File>
。 File::open()
克隆它收到的路径,所以再次,这里没有问题。
下一个是reader
,类型为BufferedReader<IoResult<File>>
。就像Path
一样,BufferedReader
的构造函数按值获取其参数并获得它的所有权。
问题在于reader.lines()
。此值的类型为Lines<'r, T: 'r>
。正如类型签名所示,此结构包含借用的引用。 lines
的签名显示了贷款人与借款人之间的关系:
fn lines<'r>(&'r mut self) -> Lines<'r, Self>
我们现在如何定义each_line_in_file_with_path
? each_line_in_file_with_path
无法直接返回Lines
。您可能尝试过编写这样的函数:
fn each_line_in_file_with_path<'a, T>(path: &T) -> Lines<'a, BufferedReader<IoResult<File>>>
where T: BytesContainer {
let path = Path::new(path);
let file = File::open(&path);
let reader = BufferedReader::new(file);
reader.lines()
}
这会产生编译错误:
main.rs:46:5: 46:11 error: `reader` does not live long enough
main.rs:46 reader.lines()
^~~~~~
main.rs:42:33: 47:2 note: reference must be valid for the lifetime 'a as defined on the block at 42:32...
main.rs:42 where T: BytesContainer {
main.rs:43 let path = Path::new(path);
main.rs:44 let file = File::open(&path);
main.rs:45 let reader = BufferedReader::new(file);
main.rs:46 reader.lines()
main.rs:47 }
main.rs:42:33: 47:2 note: ...but borrowed value is only valid for the block at 42:32
main.rs:42 where T: BytesContainer {
main.rs:43 let path = Path::new(path);
main.rs:44 let file = File::open(&path);
main.rs:45 let reader = BufferedReader::new(file);
main.rs:46 reader.lines()
main.rs:47 }
error: aborting due to previous error
这是因为我们正在尝试返回引用Lines
的{{1}},该BufferedReader
在函数返回时不再存在(Lines
将包含悬空指针)。
现在,有人可能会想,“我只会将BufferedReader
与Lines
一起返回。”
struct LinesInFileIterator<'a> {
reader: BufferedReader<IoResult<File>>,
lines: Lines<'a, BufferedReader<IoResult<File>>>
}
impl<'a> Iterator<IoResult<String>> for LinesInFileIterator<'a> {
fn next(&mut self) -> Option<IoResult<String>> {
self.lines.next()
}
}
fn each_line_in_file_with_path<'a, T>(path: &T) -> LinesInFileIterator<'a>
where T: BytesContainer {
let path = Path::new(path);
let file = File::open(&path);
let reader = BufferedReader::new(file);
LinesInFileIterator {
reader: reader,
lines: reader.lines()
}
}
这不起作用:
main.rs:46:16: 46:22 error: `reader` does not live long enough
main.rs:46 lines: reader.lines()
^~~~~~
main.rs:40:33: 48:2 note: reference must be valid for the lifetime 'a as defined on the block at 40:32...
main.rs:40 where T: BytesContainer {
main.rs:41 let path = Path::new(path);
main.rs:42 let file = File::open(&path);
main.rs:43 let reader = BufferedReader::new(file);
main.rs:44 LinesInFileIterator {
main.rs:45 reader: reader,
...
main.rs:40:33: 48:2 note: ...but borrowed value is only valid for the block at 40:32
main.rs:40 where T: BytesContainer {
main.rs:41 let path = Path::new(path);
main.rs:42 let file = File::open(&path);
main.rs:43 let reader = BufferedReader::new(file);
main.rs:44 LinesInFileIterator {
main.rs:45 reader: reader,
...
main.rs:46:16: 46:22 error: use of moved value: `reader`
main.rs:46 lines: reader.lines()
^~~~~~
main.rs:45:17: 45:23 note: `reader` moved here because it has type `std::io::buffered::BufferedReader<core::result::Result<std::io::fs::File, std::io::IoError>>`, which is non-copyable
main.rs:45 reader: reader,
^~~~~~
error: aborting due to 2 previous errors
基本上,我们不能有一个包含指向结构的另一个成员的借用引用的结构,因为当移动结构时,引用将变为无效。
有两种解决方案:
创建一个从文件路径返回BufferedReader
的函数,并在.lines()
循环中调用for
。
创建一个接受每行接收的闭包的函数。
fn main() {
for target in os::args().iter() {
scan_file(target.as_slice());
}
}
fn for_each_line_in_file_with_path_do(path: &str, action: |IoResult<String>|) {
let path = Path::new(path.as_bytes());
let file = File::open(&path);
let mut reader = BufferedReader::new(file);
for line in reader.lines() {
action(line);
}
}
fn scan_file(path_str: &str) {
for_each_line_in_file_with_path_do(path_str, |line| {
match line {
Ok(s) => {
if s.as_slice().contains("Started ") {
print!("{}", s);
}
}
Err(_) => return,
}
});
}
答案 1 :(得分:2)
如果没有一些样板,你将无法做到这一点。你需要有一些数据源,并且因为迭代器以块的形式返回它们的数据,它们要么必须包含数据,要么引用这些数据的其他来源(这也包括迭代器,从外部源返回数据,例如文件中的行。)
但是,因为你想要&#34;封装&#34;你的迭代器是一个函数调用,这个迭代器不能是第二种,即它不能包含引用,因为它可以包含的所有引用都指向这个函数调用堆栈。因此,迭代器的源只能包含在这个迭代器中。
这是样板问题 - 通常标准库中没有这样的迭代器。您需要自己创建它。但是,在这种特殊情况下,您可以在不实施Iterator
特征的情况下逃脱。您只需要创建一些简单的结构包装器:
use std::os;
use std::io::{BufferedReader, File, Lines};
fn main() {
for target in os::args().iter() {
scan_file(target.as_slice());
}
}
struct FileLines {
source: BufferedReader<File>
}
impl FileLines {
fn new(path_str: &str) -> FileLines {
let path = Path::new(path_str.as_bytes());
let file = File::open(&path).unwrap();
let reader = BufferedReader::new(file);
FileLines { source: reader }
}
fn lines(&mut self) -> Lines<BufferedReader<File>> {
self.source.lines()
}
}
fn scan_file(path_str: &str) {
for line in FileLines::new(path_str).lines() {
match line {
Ok(s) => {
if s.as_slice().contains("Started ") {
print!("{}", s);
}
}
Err(_) => return,
}
}
}
(我还将&String
更改为&str
,因为它更具惯用性和一般性)
FileLines
结构拥有数据并在其构造函数中封装所有复杂逻辑。然后它的lines()
方法只是将迭代器返回到它的内部。这是Rust中相当常见的模式,通常您将能够找到数据的主要所有者,并使用将迭代器/引用返回给此所有者的方法围绕它构建程序。
这不是完全您想要的(for
循环初始化程序中有两个函数调用 - new()
和lines()
),但我相信所有实际目的都具有相同的表现力和可用性。