85 个版本 (52 个重大更改)

0.53.1 2024年7月23日
0.52.0 2024年7月3日
0.49.2 2024年2月24日
0.46.1 2023年11月6日
0.1.0 2017年3月28日

#1 in 缓存

Download history 100770/week @ 2024-05-03 106915/week @ 2024-05-10 101025/week @ 2024-05-17 92781/week @ 2024-05-24 106372/week @ 2024-05-31 103909/week @ 2024-06-07 101222/week @ 2024-06-14 106667/week @ 2024-06-21 82908/week @ 2024-06-28 95518/week @ 2024-07-05 98303/week @ 2024-07-12 103547/week @ 2024-07-19 105148/week @ 2024-07-26 121863/week @ 2024-08-02 117104/week @ 2024-08-09 108855/week @ 2024-08-16

473,362 每月下载量
用于 331 个 Crates (150 个直接使用)

MIT 许可证

225KB
4.5K SLoC

cached

Build Status crates.io docs

缓存结构和简化函数记忆化

cached 提供了几个缓存结构的实现以及定义记忆化函数的便捷宏。

使用宏定义的缓存函数,如#[cached]#[once]#[io_cached]cached!,是线程安全的。它们的后端函数缓存被封装在互斥锁或读写锁中,或者在#[io_cached]的情况下外部同步。默认情况下,函数缓存在整个函数执行过程中不会被锁定,因此对于相同参数的长运行函数的初始(在空缓存的情况下)并发调用将每个完全执行并覆盖缓存值。这反映了Python的functools.lru_cache的行为。为了同步未缓存参数的执行和缓存,请指定#[cached(sync_writes = true)] / #[once(sync_writes = true)]#[io_cached]不支持)。

功能

  • default:包含proc_macroahash功能
  • proc_macro:包含过程宏
  • ahash:启用可选的默认散列算法ahash散列器
  • async:包含对异步函数和异步缓存存储的支持
  • async_tokio_rt_multi_thread:启用tokiort-multi-thread可选功能。
  • redis_store:包含Redis缓存存储
  • redis_async_std:包含使用async-stdasync-std的异步Redis支持,以及redis_async_std的TLS支持,隐含redis_storeasync
  • redis_tokio:包含使用tokiotokio的异步Redis支持,以及redis_tokio的TLS支持,隐含redis_storeasync
  • redis_connection_manager:启用redis的可选connection-manager功能。任何创建的异步Redis缓存都将使用连接管理器而不是MultiplexedConnection
  • redis_ahash:启用redis的可选ahash功能
  • disk_store:包含磁盘缓存存储
  • wasm:启用WASM支持。请注意,此功能与tokio的多线程运行时(async_tokio_rt_multi_thread)以及所有Redis功能(redis_storeredis_async_stdredis_tokioredis_ahash)不兼容。

过程宏(#[cached]#[once]#[io_cached])提供了更多功能,包括异步支持。请参阅proc_macromacros模块中的更多示例,以及examples目录中的可运行代码片段。

任何实现cached::Cached/cached::CachedAsync的自定义缓存都可以用于替换内置的#[cached]/#[once]/cached!宏。任何实现cached::IOCached/cached::IOCachedAsync的自定义缓存都可以用于#[io_cached]宏。


基本用法如下:

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]
fn fib(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) }"#
)]
fn keyed(a: &str, b: &str) -> usize {
    let size = a.len() + b.len();
    sleep(Duration::new(size as u64, 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)]
fn keyed(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 = true,
    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)]
enum ExampleError {
    #[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")
    } "##
)]
async fn async_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)]
enum ExampleError {
    #[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
)]
fn cached_sleep_secs(secs: u64) -> Result<String, ExampleError> {
    std::thread::sleep(std::time::Duration::from_secs(secs));
    Ok(secs.to_string())
}

通过宏定义的函数将使用函数的参数作为键,在过程宏上指定的convert表达式,或者在cached_key!声明宏上指定的Key块来缓存其结果。

当调用宏定义的函数时,首先检查函数的缓存以查找已计算(且仍然有效)的值,然后再评估函数体。

由于需要在全局缓存中存储参数和返回值的要求

  • 函数返回类型
    • 对于所有存储类型,除了Redis,必须拥有并实现Clone
    • 对于Redis存储类型,必须拥有并实现serde::Serialize + serde::DeserializeOwned
  • 函数参数
    • 对于所有存储类型,除了Redis,必须是拥有并实现Hash + Eq + Clone的类型,或者使用cached_key!宏与一个指定键构建的Key块,或者在一个过程宏上指定一个convert表达式来指定如何构建一个Hash + Eq + Clone类型的键。
    • 对于Redis存储类型,必须要么是所有者并实现Display,要么使用cached_key!Key或过程宏以及convert表达式来指定如何构造Display类型的关键字。
  • 在插入和检索过程中,参数和返回值将被cloned。除了Redis,其中参数被格式化为Strings,值被反/序列化。
  • 不应使用宏定义的函数来生成副作用结果!
  • 由于宏扩展为once_cell初始化和一个或多个函数定义,因此宏定义的函数不能直接位于impl块下。
  • 宏定义的函数不能接受Self类型作为参数。

许可证:MIT

依赖项

~1-15MB
~189K SLoC