如何管理 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中的引用。 有没有处理所有权的好方法?

需要明确的是,这是一个精简的示例。 在实际用例中,有几个文件以及其他数据。 我以这种方式构建了内容,因为它表示来自命令行选项的配置。 有些选项是文件,有些是标志。 无论哪种情况,我都想尽早对文件进行一些处理,但不是全部,以便抛出适当的错误。

我认为您在Foo结构中使用Option是按计划进行的。假设结构变为:

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并在需要时读取文件。

例如,这里有一个解决方案,可以将文件名(即 a &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的所有权,因为它是在同一函数中创建、使用和丢弃的。当然,我不完全了解您的用例,因此这可能不适合您的实现。

最新更新