如何管理Rust中的结构中保存的文件的所有权?

时间:2019-05-27 18:07:52

标签: io rust

是否有一种很好的方法来使用Rust处理结构中包含的文件的所有权?作为一个简化的示例,请考虑:

// Buffered file IO
use std::io::{BufReader,BufRead};
use std::fs::File;            

// Structure that contains a file
#[derive(Debug)]
struct Foo {                
    file : BufReader <File>,
    data : Vec <f64>,   
}   

// Reads the file and strips the header    
fn init_foo(fname : &str) -> Foo {   

    // Open the file                                      
    let mut file = BufReader::new(File::open(fname).unwrap());

    // Dump the header     
    let mut header = String::new();
    let _ = file.read_line(&mut header);

    // Return our foo
    Foo { file : file, data : Vec::new() }          
}   

// Read the remaining foo data and process it
fn read_foo(mut foo : Foo) -> Foo {

    // Strip one more line
    let mut header_alt = String::new();
    let _ = foo.file.read_line(&mut header_alt);

    // Read in the rest of the file line by line
    let mut data = Vec::new();         
    for (lineno,line) in foo.file.lines().enumerate() {

        // Strip the error
        let line = line.unwrap();

        // Print some diagnostic information    
        println!("Line {}: val {}",lineno,line);

        // Save the element
        data.push(line.parse::<f64>().unwrap());
    }   

    // Export foo
    Foo { data : data, ..foo}
}   

fn main() {

    // Initialize our foo
    let foo = init_foo("foo.txt");

    // Read in our data
    let foo = read_foo(foo); 

    // Print out some debugging info
    println!("{:?}",foo); 
} 

这当前会导致编译错误:

error[E0382]: use of moved value: `foo.file`
  --> src/main.rs:48:5
   |
35 |     for (lineno,line) in foo.file.lines().enumerate() {
   |                          -------- value moved here
...
48 |     Foo { data : data, ..foo}
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^ value used here after move
   |
   = note: move occurs because `foo.file` has type `std::io::BufReader<std::fs::File>`, which does not implement the `Copy` trait

error: aborting due to previous error

For more information about this error, try `rustc --explain E0382`.
error: Could not compile `rust_file_struct`.

To learn more, run the command again with --verbose.

而且,可以肯定的是,这是有道理的。在这里,lines()拥有缓冲文件的所有权,因此我们不能在返回值中使用该值。让我感到困惑的是处理这种情况的更好方法。当然,在for循环之后,该文件将被使用,因此它实际上无法使用。为了更好地表示这一点,我们可以将文件表示为Option <BufReader <File>>。但是,这会引起一些麻烦,因为在read_line内部的第二个read_foo调用需要对file的可变引用,而且我不确定如何获取包装在Option。是否有处理所有权的好方法?

要清楚,这是一个简化的示例。在实际的用例中,有几个文件以及其他数据。我采用这种方式构建事物,因为它表示来自命令行选项的配置。有些选项是文件,有些是标志。无论哪种情况,我都希望尽早进行一些处理,但不是全部处理,以引发适当的错误。

1 个答案:

答案 0 :(得分:1)

我认为您正在使用Option结构中的Foo。假设结构变为:

struct Foo {                
    file : Option<BufReader <File>>,
    data : Vec <f64>,   
}

以下代码是可能的解决方案:

// Reads the file and strips the header    
fn init_foo(fname : &str) -> Foo {   

    // Open the file                                      
    let mut file = BufReader::new(File::open(fname).unwrap());

    // Dump the header     
    let mut header = String::new();
    let _ = file.read_line(&mut header);

    // Return our foo
    Foo { file : Some(file), data : Vec::new() }          
}   

// Read the remaining foo data and process it
fn read_foo(foo : Foo) -> Option<Foo> {

    let mut file = foo.file?;

    // Strip one more line
    let mut header_alt = String::new();
    let _ = file.read_line(&mut header_alt);

    // Read in the rest of the file line by line
    let mut data = Vec::new();         
    for (lineno,line) in file.lines().enumerate() {

        // Strip the error
        let line = line.unwrap();

        // Print some diagnostic information    
        println!("Line {}: val {}",lineno,line);

        // Save the element
        data.push(line.parse::<f64>().unwrap());
    }   

    // Export foo
    Some(Foo { data : data, file: None})
} 

在这种情况下,由于read_foo可能是Foo,因此file返回可选的None

IMO,在旁注中,除非您绝对需要BufReaderFoo一起出游,否则我将其丢弃。正如您已经发现的那样,调用lines会导致移动,这使其很难保留在另一个结构中。作为建议,您可以将file字段简化为String,以便始终可以导出BufReader并在需要时读取文件。

例如,这是一个解决方案,其中文件名(即&str)可以转换为Foo,而所有的行处理都在构造该结构之前完成。

// Buffered file IO
use std::io::{BufReader,BufRead};
use std::fs::File;            

// Structure that contains a file
#[derive(Debug)]
struct Foo {                
    file : String,
    data : Vec <f64>,   
}   

trait IntoFoo {
    fn into_foo(self) -> Foo;
}

impl IntoFoo for &str {
    fn into_foo(self) -> Foo {
        // Open the file                                      
        let mut file = BufReader::new(File::open(self).unwrap());

        // Dump the header     
        let mut header = String::new();
        let _ = file.read_line(&mut header);

        // Strip one more line
        let mut header_alt = String::new();
        let _ = file.read_line(&mut header_alt);

        // Read in the rest of the file line by line
        let mut data = Vec::new();         
        for (lineno,line) in file.lines().enumerate() {

            // Strip the error
            let line = line.unwrap();

            // Print some diagnostic information    
            println!("Line {}: val {}",lineno,line);

            // Save the element
            data.push(line.parse::<f64>().unwrap());
        }   

        Foo { file: self.to_string(), data }
    }
}

fn main() {

    // Read in our data from the file
    let foo = "foo.txt".into_foo(); 

    // Print out some debugging info
    println!("{:?}",foo); 
} 

在这种情况下,无需担心BufReader的所有权,因为它是在同一函数中创建,使用和丢弃的。当然,我不完全了解您的用例,因此这可能不适合您的实现。