В 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 возвращал неизменяемый заимствование самого себя после изменения себя.
Пожалуйста, дайте мне знать, если есть дополнительная информация или разъяснения, которые я могу предоставить.
IIUC в этом случае RefCell<T> может быть возможным кандидатом, верно? На данный момент код однопоточный.
Извините, не могу воспроизвести ошибку. Я написал MRE с двумя предоставленными вами функциями для обновления и чтения кеша. Это компилируется без каких-либо проблем.

Один из способов решения этой проблемы — внутренняя изменчивость с использованием 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, но не требует изменения структуры или внесения дополнительных затрат.
Одним из обходных путей может быть использование внутренней изменчивости. Обычно внутренние кэши доступны «внешнему миру» через общие ссылки, поскольку изменение состояния кэша не является значимым наблюдаемым изменением, а происходит только ради производительности. См., например. мока.