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

Для ясности, это урезанный пример. В реальном случае использования есть несколько файлов, а также другие данные. Я структурировал все таким образом, потому что он представляет собой конфигурацию, полученную из параметров командной строки. Некоторые параметры являются файлами, некоторые — флагами. В любом случае я хотел бы обработать некоторые, но не все файлы заранее, чтобы выдать соответствующие ошибки.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
431
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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.

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

Другие вопросы по теме