- Notifications
You must be signed in to change notification settings - Fork103
Rust cache structures and easy function memoization
License
jaemk/cached
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Caching structures and simplified function memoization
cached
provides implementations of several caching structures as well as a handy macrosfor defining memoized functions.
Memoized functions defined using#[cached]
/#[once]
/#[io_cached]
/cached!
macros are thread-safe with the backingfunction-cache wrapped in a mutex/rwlock, or externally synchronized in the case of#[io_cached]
.By default, the function-cache isnot locked for the duration of the function's execution, so initial (on an empty cache)concurrent calls of long-running functions with the same arguments will each execute fully and each overwritethe memoized value as they complete. This mirrors the behavior of Python'sfunctools.lru_cache
. To synchronize the execution and cachingof un-cached arguments, specify#[cached(sync_writes = "default")]
/#[once(sync_writes = true)]
(not supported by#[io_cached]
.
- See
cached::stores
docs cache stores available. - See
proc_macro
for more procedural macro examples. - See
macros
for more declarative macro examples.
Features
default
: Includeproc_macro
andahash
featuresproc_macro
: Include proc macrosahash
: Enable the optionalahash
hasher as default hashing algorithm.async
: Include support for async functions and async cache storesasync_tokio_rt_multi_thread
: Enabletokio
's optionalrt-multi-thread
feature.redis_store
: Include Redis cache storeredis_async_std
: Include async Redis support usingasync-std
andasync-std
tls support, impliesredis_store
andasync
redis_tokio
: Include async Redis support usingtokio
andtokio
tls support, impliesredis_store
andasync
redis_connection_manager
: Enable the optionalconnection-manager
feature ofredis
. Any async redis caches createdwill use a connection manager instead of aMultiplexedConnection
redis_ahash
: Enable the optionalahash
feature ofredis
disk_store
: Include disk cache storewasm
: Enable WASM support. Note that this feature is incompatible withtokio
's multi-threadruntime (async_tokio_rt_multi_thread
) and all Redis features (redis_store
,redis_async_std
,redis_tokio
,redis_ahash
)
The procedural macros (#[cached]
,#[once]
,#[io_cached]
) offer more features, including async support.See theproc_macro
andmacros
modules for more samples, and theexamples
directory for runnable snippets.
Any custom cache that implementscached::Cached
/cached::CachedAsync
can be used with the#[cached]
/#[once]
/cached!
macros in place of the built-ins.Any custom cache that implementscached::IOCached
/cached::IOCachedAsync
can be used with the#[io_cached]
macro.
The basic usage looks like:
use cached::proc_macro::cached;/// Defines a function named `fib` that uses a cache implicitly named `FIB`./// By default, the cache will be the function's name in all caps./// The following line is equivalent to #[cached(name = "FIB", unbound)]#[cached]fnfib(n:u64) ->u64{if n ==0 || n ==1{return n}fib(n-1) +fib(n-2)}
use std::thread::sleep;use std::time::Duration;use cached::proc_macro::cached;use cached::SizedCache;/// Use an explicit cache-type with a custom creation block and custom cache-key generating block#[cached( ty ="SizedCache<String, usize>", create ="{ SizedCache::with_size(100) }", convert =r#"{ format!("{}{}", a, b) }"#)]fnkeyed(a:&str,b:&str) ->usize{let size = a.len() + b.len();sleep(Duration::new(sizeasu64,0)); size}
use cached::proc_macro::once;/// Only cache the initial function call./// Function will be re-executed after the cache/// expires (according to `time` seconds)./// When no (or expired) cache, concurrent calls/// will synchronize (`sync_writes`) so the function/// is only executed once.#[once(time=10, option =true, sync_writes =true)]fnkeyed(a:String) ->Option<usize>{if a =="a"{Some(a.len())}else{None}}
use cached::proc_macro::cached;/// Cannot use sync_writes and result_fallback together#[cached( result = true, time = 1, sync_writes = "default", result_fallback = true)]fn doesnt_compile() -> Result<String, ()> { Ok("a".to_string())}
use cached::proc_macro::io_cached;use cached::AsyncRedisCache;use thiserror::Error;#[derive(Error,Debug,PartialEq,Clone)]enumExampleError{#[error("error with redis cache `{0}`")]RedisError(String),}/// Cache the results of an async function in redis. Cache/// keys will be prefixed with `cache_redis_prefix`./// A `map_error` closure must be specified to convert any/// redis cache errors into the same type of error returned/// by your function. All `io_cached` functions must return `Result`s.#[io_cached( map_error =r##"|e| ExampleError::RedisError(format!("{:?}", e))"##, ty ="AsyncRedisCache<u64, String>", create =r##" { AsyncRedisCache::new("cached_redis_prefix", 1) .set_refresh(true) .build() .await .expect("error building example redis cache") } "##)]asyncfnasync_cached_sleep_secs(secs:u64) ->Result<String,ExampleError>{ std::thread::sleep(std::time::Duration::from_secs(secs));Ok(secs.to_string())}
use cached::proc_macro::io_cached;use cached::DiskCache;use thiserror::Error;#[derive(Error,Debug,PartialEq,Clone)]enumExampleError{#[error("error with disk cache `{0}`")]DiskError(String),}/// Cache the results of a function on disk./// Cache files will be stored under the system cache dir/// unless otherwise specified with `disk_dir` or the `create` argument./// A `map_error` closure must be specified to convert any/// disk cache errors into the same type of error returned/// by your function. All `io_cached` functions must return `Result`s.#[io_cached( map_error =r##"|e| ExampleError::DiskError(format!("{:?}", e))"##, disk =true)]fncached_sleep_secs(secs:u64) ->Result<String,ExampleError>{ std::thread::sleep(std::time::Duration::from_secs(secs));Ok(secs.to_string())}
Functions defined via macros will have their results cached using thefunction's arguments as a key, aconvert
expression specified on a procedural macros,or aKey
block specified on acached_key!
declarative macro.
When a macro-defined function is called, the function's cache is first checked for an alreadycomputed (and still valid) value before evaluating the function body.
Due to the requirements of storing arguments and return values in a global cache:
- Function return types:
- For all store types, except Redis, must be owned and implement
Clone
- For the Redis store type, must be owned and implement
serde::Serialize + serde::DeserializeOwned
- For all store types, except Redis, must be owned and implement
- Function arguments:
- For all store types, except Redis, must either be owned and implement
Hash + Eq + Clone
,thecached_key!
macro is used with aKey
block specifying key construction, oraconvert
expression is specified on a procedural macro to specify how to construct a keyof aHash + Eq + Clone
type. - For the Redis store type, must either be owned and implement
Display
, or thecached_key!
&Key
or procedural macro &convert
expression used to specify how to construct a key of aDisplay
type.
- For all store types, except Redis, must either be owned and implement
- Arguments and return values will be
cloned
in the process of insertion and retrieval. Except for Rediswhere arguments are formatted intoStrings
and values are de/serialized. - Macro-defined functions should not be used to produce side-effectual results!
- Macro-defined functions cannot live directly under
impl
blocks since macros expand to aonce_cell
initialization and one or more function definitions. - Macro-defined functions cannot accept
Self
types as a parameter.
License: MIT
About
Rust cache structures and easy function memoization
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.