Как я могу реализовать операцию выборки-кэширования или загрузки в Rust? - PullRequest
0 голосов
/ 20 октября 2019

Я пытаюсь создать простой кеш, в котором есть одна операция: «получить из кеша, загружая при необходимости». Вот рабочий пример (просто для простоты используется загрузка файла):

use std::collections::HashMap;
use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};

#[derive(Debug, Default)]
pub struct FileCache {
    /// Map storing contents of loaded files.
    files: HashMap<PathBuf, Vec<u8>>,
}

impl FileCache {
    /// Get a file's contents, loading it if it hasn't yet been loaded.
    pub fn get(&mut self, path: &Path) -> io::Result<&[u8]> {
        if let Some(_) = self.files.get(path) {
            println!("Cached");
            return Ok(self.files.get(path).expect("just checked"));
        }
        let buf = self.load(path)?;
        Ok(self.files.entry(path.to_owned()).or_insert(buf))
    }
    /// Load a file, returning its contents.
    fn load(&self, path: &Path) -> io::Result<Vec<u8>> {
        println!("Loading");
        let mut buf = Vec::new();
        use std::io::Read;
        File::open(path)?.read_to_end(&mut buf)?;
        Ok(buf)
    }
}

pub fn main() -> io::Result<()> {
    let mut store = FileCache::default();
    let path = Path::new("src/main.rs");
    println!("Length: {}", store.get(path)?.len());
    println!("Length: {}", store.get(path)?.len());
    Ok(())
}

В ветви успеха if let есть дополнительный вызов self.files.get и дополнительный expect,Мы только что назвали это и сопоставили с шаблоном по его результату, поэтому мы хотели бы просто вернуть совпадение:

    pub fn get(&mut self, path: &Path) -> io::Result<&[u8]> {
        if let Some(x) = self.files.get(path) {
            println!("Cached");
            return Ok(x);
        }
        let buf = self.load(path)?;
        Ok(self.files.entry(path.to_owned()).or_insert(buf))
    }

Но этот не проходит проверку заимствования :

error[E0502]: cannot borrow `self.files` as mutable because it is also borrowed as immutable
  --> src/main.rs:20:12
   |
14 |     pub fn get(&mut self, path: &Path) -> io::Result<&[u8]> {
   |                - let's call the lifetime of this reference `'1`
15 |         if let Some(x) = self.files.get(path) {
   |                          ---------- immutable borrow occurs here
16 |             println!("Cached");
17 |             return Ok(x);
   |                    ----- returning this value requires that `self.files` is borrowed for `'1`
...
20 |         Ok(self.files.entry(path.to_owned()).or_insert(buf))
   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

Я не понимаю, почему эти две версии ведут себя по-разному. Разве self.files не заимствовано для &self срока службы в обоих случаях? В первой форме мы отбрасываем заем и получаем новый, но я не понимаю, почему это должно иметь значение. Как вторая форма позволяет мне нарушать безопасность памяти, и как я могу написать этот код без дополнительного поиска и expect проверки?

Я прочитал Как мне написать функцию ржавчины, котораяможет и читать, и записывать в кеш? , что связано, но ответы там либо дублируют поиск (как в моем рабочем примере), либо клонируют значение (непозволительно дорого), поэтому этого недостаточно.

1 Ответ

1 голос
/ 20 октября 2019

Обе реализации должны быть допустимыми кодами Rust. Тот факт, что rustc отклонил второй, на самом деле является ошибкой, которую отслеживает проблема 58910 .

Давайте уточним:

pub fn get(&mut self, path: &Path) -> io::Result<&[u8]> {
    if let Some(x) = self.files.get(path) { // ---+ x
        println!("Cached");                    // |
        return Ok(x);                          // |
    }                                          // |
    let buf = self.load(path)?;                // |
    Ok(self.files.entry(path.to_owned())       // |
        .or_insert(buf))                       // |
}                                              // v

При привязке к переменной x в некотором выражении self.files заимствовано как неизменное. Тот же x позже возвращается вызывающему, что означает, что self.files остается заимствованным до некоторой точки в вызывающем. Таким образом, в текущей реализации средства проверки заимствования Rust self.files равно , излишне постоянно заимствовано всю функцию get. Это не может быть заимствовано позже как изменчивое. Тот факт, что x никогда не используется после досрочного возврата, не учитывается.

Ваша первая реализация работает в качестве обходного пути. Поскольку let some(_) = ... не создает привязки, у него нет такой же проблемы. Это требует дополнительных затрат времени выполнения из-за нескольких поисков.

Это проблемный случай # 3 , описанный в NLL RFC от Niko. Хорошие новости:

TL; DR: Полоний должен это исправить.

( Полоний - более сложное 3-е воплощение Rust заимствованияшашка, которая все еще находится в разработке).

...