Как управлять владением файлом, хранящимся в структуре в Rust? - PullRequest
0 голосов
/ 27 мая 2019

Есть ли хороший способ обработать владение файлом, хранящимся в структуре, используя 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 Ответ

1 голос
/ 28 мая 2019

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

...