委托创建数据结构

时间:2014-11-23 21:50:16

标签: rust

我对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应该处理所有“将路径转换为行”,返回迭代器。

我已经尝试了许多不成功的事情,总是由于变量超出范围而对我的需求太早。我理解我遇到的问题(我认为),但无法找到解释如何处理的问题。

2 个答案:

答案 0 :(得分:3)

不可能实现一个有效的each_line_in_file_with_path函数 - 至少,不是没有添加一些开销和不安全的代码。

让我们看看所涉及的价值及其类型。首先是path,类型为Pathposix::Pathwindows::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_patheach_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将包含悬空指针)。

现在,有人可能会想,“我只会将BufferedReaderLines一起返回。”

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

基本上,我们不能有一个包含指向结构的另一个成员的借用引用的结构,因为当移动结构时,引用将变为无效。

有两种解决方案:

  1. 创建一个从文件路径返回BufferedReader的函数,并在.lines()循环中调用for

  2. 创建一个接受每行接收的闭包的函数。

    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()),但我相信所有实际目的都具有相同的表现力和可用性。