Struct mini_moka::unsync::Cache

source ·
pub struct Cache<K, V, S = RandomState> { /* private fields */ }
Expand description

An in-memory cache that is not thread-safe.

Cache utilizes a hash table std::collections::HashMap from the standard library for the central key-value storage. Cache performs a best-effort bounding of the map using an entry replacement algorithm to determine which entries to evict when the capacity is exceeded.

Characteristic difference between unsync and sync/future caches

If you use a cache from a single thread application, unsync::Cache may outperform other caches for updates and retrievals because other caches have some overhead on syncing internal data structures between threads.

However, other caches may outperform unsync::Cache on the same operations when expiration polices are configured on a multi-core system. unsync::Cache evicts expired entries as a part of update and retrieval operations while others evict them using a dedicated background thread.

Examples

Cache entries are manually added using the insert method, and are stored in the cache until either evicted or manually invalidated.

Here’s an example of reading and updating a cache by using the main thread:

 use mini_moka::unsync::Cache;

 const NUM_KEYS: usize = 64;

 fn value(n: usize) -> String {
     format!("value {}", n)
 }

 // Create a cache that can store up to 10,000 entries.
 let mut cache = Cache::new(10_000);

 // Insert 64 entries.
 for key in 0..NUM_KEYS {
     cache.insert(key, value(key));
 }

 // Invalidate every 4 element of the inserted entries.
 for key in (0..NUM_KEYS).step_by(4) {
     cache.invalidate(&key);
 }

 // Verify the result.
 for key in 0..NUM_KEYS {
     if key % 4 == 0 {
         assert_eq!(cache.get(&key), None);
     } else {
         assert_eq!(cache.get(&key), Some(&value(key)));
     }
 }

Size-based Eviction

use std::convert::TryInto;
use mini_moka::unsync::Cache;

// Evict based on the number of entries in the cache.
let mut cache = Cache::builder()
    // Up to 10,000 entries.
    .max_capacity(10_000)
    // Create the cache.
    .build();
cache.insert(1, "one".to_string());

// Evict based on the byte length of strings in the cache.
let mut cache = Cache::builder()
    // A weigher closure takes &K and &V and returns a u32
    // representing the relative size of the entry.
    .weigher(|_key, value: &String| -> u32 {
        value.len().try_into().unwrap_or(u32::MAX)
    })
    // This cache will hold up to 32MiB of values.
    .max_capacity(32 * 1024 * 1024)
    .build();
cache.insert(2, "two".to_string());

If your cache should not grow beyond a certain size, use the max_capacity method of the CacheBuilder to set the upper bound. The cache will try to evict entries that have not been used recently or very often.

At the cache creation time, a weigher closure can be set by the weigher method of the CacheBuilder. A weigher closure takes &K and &V as the arguments and returns a u32 representing the relative size of the entry:

  • If the weigher is not set, the cache will treat each entry has the same size of 1. This means the cache will be bounded by the number of entries.
  • If the weigher is set, the cache will call the weigher to calculate the weighted size (relative size) on an entry. This means the cache will be bounded by the total weighted size of entries.

Note that weighted sizes are not used when making eviction selections.

Time-based Expirations

Cache supports the following expiration policies:

  • Time to live: A cached entry will be expired after the specified duration past from insert.
  • Time to idle: A cached entry will be expired after the specified duration past from get or insert.

See the CacheBuilder’s doc for how to configure a cache with them.

Hashing Algorithm

By default, Cache uses a hashing algorithm selected to provide resistance against HashDoS attacks. It will the same one used by std::collections::HashMap, which is currently SipHash 1-3.

While SipHash’s performance is very competitive for medium sized keys, other hashing algorithms will outperform it for small keys such as integers as well as large keys such as long strings. However those algorithms will typically not protect against attacks such as HashDoS.

The hashing algorithm can be replaced on a per-Cache basis using the build_with_hasher method of the CacheBuilder. Many alternative algorithms are available on crates.io, such as the aHash crate.

Implementations§

source§

impl<K, V> Cache<K, V, RandomState>
where K: Hash + Eq,

source

pub fn new(max_capacity: u64) -> Self

Constructs a new Cache<K, V> that will store up to the max_capacity entries.

To adjust various configuration knobs such as initial_capacity or time_to_live, use the CacheBuilder.

source

pub fn builder() -> CacheBuilder<K, V, Cache<K, V, RandomState>>

Returns a CacheBuilder, which can builds a Cache with various configuration knobs.

source§

impl<K, V, S> Cache<K, V, S>

source

pub fn policy(&self) -> Policy

Returns a read-only cache policy of this cache.

At this time, cache policy cannot be modified after cache creation. A future version may support to modify it.

source

pub fn entry_count(&self) -> u64

Returns the number of entries in this cache.

Example
use mini_moka::unsync::Cache;

let mut cache = Cache::new(10);
cache.insert('n', "Netherland Dwarf");
cache.insert('l', "Lop Eared");
cache.insert('d', "Dutch");

// Ensure an entry exists.
assert!(cache.contains_key(&'n'));

// Followings will print the actual numbers.
println!("{}", cache.entry_count());   // -> 3
println!("{}", cache.weighted_size()); // -> 3
source

pub fn weighted_size(&self) -> u64

Returns the total weighted size of entries in this cache.

See entry_count for a sample code.

source§

impl<K, V, S> Cache<K, V, S>
where K: Hash + Eq, S: BuildHasher + Clone,

source

pub fn contains_key<Q>(&mut self, key: &Q) -> bool
where Rc<K>: Borrow<Q>, Q: Hash + Eq + ?Sized,

Returns true if the cache contains a value for the key.

Unlike the get method, this method is not considered a cache read operation, so it does not update the historic popularity estimator or reset the idle timer for the key.

The key may be any borrowed form of the cache’s key type, but Hash and Eq on the borrowed form must match those for the key type.

source

pub fn get<Q>(&mut self, key: &Q) -> Option<&V>
where Rc<K>: Borrow<Q>, Q: Hash + Eq + ?Sized,

Returns an immutable reference of the value corresponding to the key.

The key may be any borrowed form of the cache’s key type, but Hash and Eq on the borrowed form must match those for the key type.

source

pub fn insert(&mut self, key: K, value: V)

Inserts a key-value pair into the cache.

If the cache has this key present, the value is updated.

source

pub fn invalidate<Q>(&mut self, key: &Q)
where Rc<K>: Borrow<Q>, Q: Hash + Eq + ?Sized,

Discards any cached value for the key.

The key may be any borrowed form of the cache’s key type, but Hash and Eq on the borrowed form must match those for the key type.

source

pub fn invalidate_all(&mut self)

Discards all cached values.

Like the invalidate method, this method does not clear the historic popularity estimator of keys so that it retains the client activities of trying to retrieve an item.

source

pub fn invalidate_entries_if(&mut self, predicate: impl FnMut(&K, &V) -> bool)

Discards cached values that satisfy a predicate.

invalidate_entries_if takes a closure that returns true or false. invalidate_entries_if will apply the closure to each cached value, and if the closure returns true, the value will be invalidated.

Like the invalidate method, this method does not clear the historic popularity estimator of keys so that it retains the client activities of trying to retrieve an item.

source

pub fn iter(&self) -> Iter<'_, K, V, S>

Creates an iterator visiting all key-value pairs in arbitrary order. The iterator element type is (&K, &V).

Unlike the get method, visiting entries via an iterator do not update the historic popularity estimator or reset idle timers for keys.

Examples
use mini_moka::unsync::Cache;

let mut cache = Cache::new(100);
cache.insert("Julia", 14);

let mut iter = cache.iter();
let (k, v) = iter.next().unwrap(); // (&K, &V)
assert_eq!(k, &"Julia");
assert_eq!(v, &14);

assert!(iter.next().is_none());

Trait Implementations§

source§

impl<K, V, S> Debug for Cache<K, V, S>
where K: Debug + Eq + Hash, V: Debug, S: BuildHasher + Clone,

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<K, V, S = RandomState> !RefUnwindSafe for Cache<K, V, S>

§

impl<K, V, S = RandomState> !Send for Cache<K, V, S>

§

impl<K, V, S = RandomState> !Sync for Cache<K, V, S>

§

impl<K, V, S> Unpin for Cache<K, V, S>
where S: Unpin, V: Unpin,

§

impl<K, V, S = RandomState> !UnwindSafe for Cache<K, V, S>

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.