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 to get() and manually call clear() 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 the MemoOnce 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