Crate core_memo[−][src]
Simple Memoization Library
core_memo
is a simple, straightforward, zero-cost library for lazy
evaluation and memoization. It does not do memory allocations or dynamic
dispatch and it is #![no_std]
-compatible.
You must define a custom type to represent your computation and implement
the Memoize
trait for it. Then, you can use it with the Memo
, MemoExt
,
or MemoOnce
types to lazily evaluate and cache the value.
Here is an example:
use core_memo::{Memoize, Memo, MemoExt}; #[derive(Debug, PartialEq, Eq)] // for assert_eq! later struct MemoSum(i32); impl Memoize for MemoSum { type Param = [i32]; fn memoize(p: &[i32]) -> MemoSum { MemoSum(p.iter().sum()) } } // The `Memo` type holds ownership over the parameter for the calculation let mut memo: Memo<MemoSum, _> = Memo::new(vec![1, 2]); // Our `memoize` method is called the first time we call `memo.get()` assert_eq!(memo.get(), &MemoSum(3)); // Further calls to `memo.get()` return the cached value without reevaluating assert_eq!(memo.get(), &MemoSum(3)); // We can mutate the parameter held inside the `Memo`: // via a mutable reference memo.param_mut().push(3); // via a closure memo.update_param(|p| p.push(4)); // either way, the `Memo` forgets any cached value and it will be // reevaluated on the next call to `memo.get()` assert_eq!(memo.get(), &MemoSum(10)); // the vec is now `[1, 2, 3, 4]`
There are 3 different wrapper types: Memo
, MemoExt
, and MemoOnce
.
-
Memo
contains / holds ownership over the parameter for the computation. This makes it the easiest and safest to use, but could limit your flexibility in more complex scenarios. -
MemoExt
does not keep track of the parameter. This means that you have to provide it externally with every call toget()
and manually callclear()
whenever the value needs to be reevaluated. This makes it more cumbersome and error-prone, but gives you full flexibility to manage the input parameter however you want. -
MemoOnce
holds a shared reference to the parameter. This lets you manage the parameter externally, but you cannot mutate it as long as theMemoOnce
is alive. This could be useful for one-off computations.
Implementation Notes
Why do the types not implement Deref
/DerefMut
?
Deref
takes &self
, so it cannot mutate the object to cache the result
of the computation. DerefMut
requires Deref
.
Also, while such implementations would save some typing by making the use
implicit, I believe this is undesirable. It is against the spirit of Rust to
make potentially-expensive computations implicit and hide them. This is why
.clone()
is explicit and why Cell
/RefCell
need explicit method calls.
Why not use interior mutability?
The design of this library follows KISS principles. It should be simple, straightforward, composable. No hidden magic. No surprises.
You are free to wrap these objects in whatever you like if you need to use them in an immutable context.
The current design of the library makes it as widely-useful as possible.
Structs
Memo |
Memoized value which holds ownership over the parameter for its computation |
MemoExt |
Memoized value with a parameter provided externally |
MemoOnce |
Memoized value which holds a reference to the parameter for its computation |
Traits
Memoize |
Represents a computation that is to be memoized |