Каков идиоматический способ реализации кэширования для функции, которая не является методом структуры?


У меня есть такая дорогая функция:

pub fn get_expensive_value(n: u64): u64 {
   let ret = 0;
   for 0 .. n {
       // expensive stuff
   }
   ret
}

И он вызывается очень часто с одним и тем же аргументом. Он чистый, так что это означает, что он вернет тот же результат и может использовать кэш.

Если бы это был метод struct, я бы добавил член в структуру, которая действует как кэш, но это не так. поэтому мой вариант, кажется, использовать статический:

static mut LAST_VAL: Option<(u64, u64)> = None;

pub fn cached_expensive(n: u64) -> u64 {
   unsafe {
       LAST_VAL = LAST_VAL.and_then(|(k, v)| {
           if k == n {
              Some((n,v))
           } else {
              None
           }
       }).or_else(|| {
           Some((n, get_expensive_value(n)))
       });
       let (_, v) = LAST_VAL.unwrap();
       v
   }
}
Теперь мне пришлось использовать unsafe. Вместо static mut я мог бы поместить RefCell в const. Но я не уверен, что это так. безопаснее-он просто избегает необходимости использовать блок unsafe. Я думал о Mutex, но я не думаю, что это поможет мне обеспечить безопасность потока.

Перепроектирование кода для использования структуры в качестве хранилища на самом деле не является вариантом.

1 4

1 ответ:

Я думаю, что лучшей альтернативой является использование глобальной переменной с мьютексом. Использование lazy_static упрощает задачу и позволяет использовать "глобальное" объявление внутри функции

pub fn cached_expensive(n: u64) -> u64 {
    use std::sync::Mutex;
    lazy_static! {
        static ref LAST_VAL: Mutex<Option<(u64, u64)>> = Mutex::new(None);
    }
    let mut last = LAST_VAL.lock().unwrap();
    let r = last.and_then(|(k, v)| {
        if k == n {
            Some((n, v))
        } else {
            None
        }
    }).or_else(|| Some((n, get_expensive_value(n))));
    let (_, v) = r.unwrap();
    *last = r;
    v
}