Rust: вернуть неизменяемый заем после изменения

В Rust я пытаюсь обновить кеш, а затем вернуть результат.

fn cached_res(&mut self) -> &Vec<u64> {
  if self.last_update.elapsed() >= REFRESH_RATE {
    self.a = do_something(...);
  }
  return &self.a;
}

fn use_cached_res(&mut self) {
  let b = self.cached_res();
  b.iter().map(|x| x + 1).collect()
}

И компиляция не удалась из-за

173 |         let b = cached_res(self);
    |                            ---- mutable borrow occurs here
...
179 |                 .map(|f| self.do_something(&f))
    |                  --- ^^^ ---- second borrow occurs due to use of `*self` in closure
    |                  |   |
    |                  |   immutable borrow occurs here
    |                  mutable borrow later used by call

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

fn refresh(&mut self) {
  if self.last_update.elapsed() >= REFRESH_RATE {
    self.a = do_something(...);
  }
}
fn res(&self) -> &Vec<u64> {
  &self.a
}
fn use_cached_res(&mut self) {
  self.refresh();
  let b = self.res();
  b.iter().map(|x| x + 1).collect()
}

Однако мне бы хотелось, чтобы эти ограничения были определены в cached_res, чтобы последующим пользователям не приходилось использовать такие методы в правильной последовательности. IIUC я хочу, чтобы cached_res возвращал неизменяемый заимствование самого себя после изменения себя.

Пожалуйста, дайте мне знать, если есть дополнительная информация или разъяснения, которые я могу предоставить.

Одним из обходных путей может быть использование внутренней изменчивости. Обычно внутренние кэши доступны «внешнему миру» через общие ссылки, поскольку изменение состояния кэша не является значимым наблюдаемым изменением, а происходит только ради производительности. См., например. мока.

cdhowie 13.03.2024 22:58

IIUC в этом случае RefCell<T> может быть возможным кандидатом, верно? На данный момент код однопоточный.

user23486822 13.03.2024 23:14

Извините, не могу воспроизвести ошибку. Я написал MRE с двумя предоставленными вами функциями для обновления и чтения кеша. Это компилируется без каких-либо проблем.

Kaplan 18.04.2024 16:28
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
3
90
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Один из способов решения этой проблемы — внутренняя изменчивость с использованием RefCell:

use std::cell::RefCell;
use std::ops::Deref;
use std::time::{Duration, Instant};

const REFRESH_RATE: Duration = Duration::from_millis(16);

pub struct A {
    a: RefCell<Vec<u64>>,
    last_update: Instant,
}

impl A {
    fn cached_res(&self) -> impl Deref<Target = Vec<u64>> + '_ {
        if self.last_update.elapsed() >= REFRESH_RATE {
            *self.a.borrow_mut() = Vec::new();
        }
        self.a.borrow()
    }

    pub fn use_cached_res(&self) -> Vec<u64> {
        let b = self.cached_res();
        b.iter().map(|&x| self.process(x)).collect()
    }

    fn process(&self, _x: u64) -> u64 {
        todo!()
    }
}

Это потребует дополнительных затрат на отслеживание заимствований во время выполнения, но даст вам максимальную гибкость и даже позволит вам вернуться impl DerefMut, если хотите. Вы можете легко перевести это в RwLock (borrow_mut -> write и borrow -> read) или Mutex (lock), если вам нужно, чтобы эта структура была Sync.

Другой способ — инвертировать поток управления, выполнив замыкание:

use std::time::{Duration, Instant};

const REFRESH_RATE: Duration = Duration::from_millis(16);

pub struct A {
    a: Vec<u64>,
    last_update: Instant,
}

impl A {
    fn cached_res<F, R>(&mut self, f: F) -> R
    where
        F: FnOnce(&Self, &Vec<u64>) -> R,
    {
        if self.last_update.elapsed() >= REFRESH_RATE {
            self.a = Vec::new();
        }
        f(self, &self.a)
    }

    pub fn use_cached_res(&mut self) -> Vec<u64> {
        self.cached_res(|self_, b| b.iter().map(|&x| self_.process(x)).collect())
    }

    fn process(&self, _x: u64) -> u64 {
        todo!()
    }
}

Это накладывает ограничения на то, как вы можете использовать cached_res, но не требует изменения структуры или внесения дополнительных затрат.

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