Раскрытие деталей реализации в чертах Rust - PullRequest
0 голосов
/ 08 февраля 2020

Я новичок в Rust и, пришедший из мира Java, я хотел играть с чертой Rust, как и с Java интерфейсами. Я представил следующую потребность:

  • Я должен быть в состоянии сохранить пользователя (имя, фамилию) где-нибудь (в БД, файл)
  • Я могу получить все из них

Я начал определять черту, которую хотел иметь:

trait UserDb {
    fn get_all(&self) -> Result<Vec<User>, io::Error>;

    fn save(&mut self, user: &User) -> Result<(), io::Error>;
}

Вы видите, что когда я объявляю функцию get_all, я не упоминаю о необходимости иметь изменчивый заем на self (т.е. &mut self).

Затем я решил реализовать эту черту с возможностями File (пожалуйста, найдите полный код в конце).

Что меня удивило, так это то, что когда я читаю содержимое файла, у меня объявить self изменчивым. (вот причина: Почему файл должен быть изменяемым для вызова Read :: read_to_string? )

Это раздражает меня, потому что, если я это сделаю, я должен объявить в черте self как изменчивый, даже если я читаю данные. Я чувствую, что в этой черте есть утечка деталей реализации.

Я думаю, что мой подход неверен или идиоматизм c в Rust. Как бы вы достигли этого?

Вот полный код:

///THIS CODE DOESNT COMPILE
///THE COMPILER TELLS TO MAKE self AS MUTABLE
use std::fs::File;
use std::fs::OpenOptions;
use std::io;
use std::path::Path;
use std::io::Read;
use std::io::Write;

struct User {
    pub firstname: String,
    pub lastname: String,
}

trait UserDb {
    fn get_all(&self) -> Result<Vec<User>, io::Error>;

    fn save(&mut self, user: &User) -> Result<(), io::Error>;
}

struct FsUserDb {
    pub file: File,
}

impl FsUserDb {
    fn new(filename: &str) -> Result<FsUserDb, io::Error> {
        if Path::new(filename).exists() {
            let file = OpenOptions::new()
                .append(true)
                .write(true)
                .open(filename)?;

            Ok(FsUserDb { file })
        } else {
            Ok(FsUserDb {
                file: File::create(filename)?,
            })
        }
    }
}

impl UserDb for FsUserDb {
    fn get_all(&self) -> Result<Vec<User>, io::Error> {
        let mut contents = String::new();

        self.file.read_to_string(&mut contents)?;

        let users = contents
            .lines()
            .map(|line| line.split(";").collect::<Vec<&str>>())
            .map(|split_line| User {
                firstname: split_line[0].to_string(),
                lastname: split_line[1].to_string(),
            })
            .collect();

        Ok(users)
    }

    fn save(&mut self, user: &User) -> Result<(), io::Error> {
        let user_string =
            format!("{},{}", user.firstname, user.lastname);

        match self.file.write(user_string.as_bytes()) {
            Ok(_) => Ok(()),
            Err(e) => Err(e)
        }
    }
}

fn main() {
    let db = FsUserDb::new("/tmp/user-db");
}

1 Ответ

1 голос
/ 08 февраля 2020

read требуется изменяемый заем, с этим мало что можно сделать.

Чтобы решить вашу проблему, есть три варианта, которые я могу придумать:

  • Измените свою подпись на черте на &mut self, как рекомендует компилятор. Это самое ясное решение, я не уверен, почему оно вам не нравится.

  • Используйте внутреннюю изменчивость, например RefCell, и получите изменяемую File там, где вам это нужно. С этим решением вам даже не нужно объявлять save изменяемым, но добавляет некоторые затраты времени выполнения. Я рекомендую прочитать о RefCell, поскольку в дальнейшем это может привести к другим видам ошибок.

  • Храните имя файла, а не сам обработчик File, и открывайте / закрывайте его при необходимости. При этом вы также можете использовать неизменяемый save.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...