Есть ли хороший способ управлять владением файлом, хранящимся в структуре, с помощью 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
. Есть ли хороший способ справиться с владением?
Для ясности, это урезанный пример. В реальном случае использования есть несколько файлов, а также другие данные. Я структурировал все таким образом, потому что он представляет собой конфигурацию, полученную из параметров командной строки. Некоторые параметры являются файлами, некоторые — флагами. В любом случае я хотел бы обработать некоторые, но не все файлы заранее, чтобы выдать соответствующие ошибки.
Я думаю, вы на пути к использованию 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
.
Кстати, ИМО, если вам абсолютно не нужно, чтобы BufReader
был путешествие вместе с Foo
, я бы отказался от него. Как вы уже обнаружили, вызов 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
, потому что он создается, используется и отбрасывается в одной и той же функции. Конечно, я не полностью знаю ваш вариант использования, поэтому это может не подойти для вашей реализации.