cached

Rustの強力なメモ化・キャッシュライブラリ。プロシージャルマクロによる簡単な統合と、多様なキャッシュバックエンドをサポート。

GitHub概要

jaemk/cached

Rust cache structures and easy function memoization

スター1,900
ウォッチ12
フォーク107
作成日:2017年4月2日
言語:Rust
ライセンス:MIT License

トピックス

cachecachinglrucachememoizationrustrust-cachingrustlang

スター履歴

jaemk/cached Star History
データ取得日時: 2025/10/22 09:23

概要

cachedは、Rustアプリケーション向けの包括的なキャッシュ・メモ化ライブラリです。プロシージャルマクロを使用して、関数に簡単にキャッシュ機能を追加できます。関数の結果を自動的に保存し、同じ引数で再度呼び出された際にキャッシュから結果を返すことで、パフォーマンスを大幅に向上させます。

主な特徴

  • プロシージャルマクロ: #[cached]属性で簡単に関数をメモ化
  • 多様なキャッシュタイプ: UnboundCache、SizedCache、TimedCache、TimedSizedCacheなど
  • 非同期サポート: async関数のキャッシングに対応
  • Result型対応: エラーをキャッシュしないオプション
  • カスタマイズ可能: キーの変換、キャッシュの初期化などを柔軟に設定
  • スレッドセーフ: マルチスレッド環境での安全な使用

インストール

[dependencies]
cached = "0.49"

# オプション:async機能を使用する場合
cached = { version = "0.49", features = ["async"] }

# オプション:Redis バックエンドを使用する場合
cached = { version = "0.49", features = ["redis_store", "async"] }

基本的な使い方

シンプルなメモ化

use cached::proc_macro::cached;

// 基本的なメモ化
#[cached]
fn fibonacci(n: u64) -> u64 {
    if n <= 1 {
        return n;
    }
    fibonacci(n - 1) + fibonacci(n - 2)
}

fn main() {
    // 初回は計算される
    let result = fibonacci(40);
    println!("fibonacci(40) = {}", result);
    
    // 2回目以降はキャッシュから返される
    let result = fibonacci(40);
    println!("fibonacci(40) = {} (cached)", result);
}

時間ベースのキャッシュ

use cached::proc_macro::cached;
use std::time::Duration;

// 5秒間キャッシュする
#[cached(time = 5)]
fn get_current_timestamp() -> u64 {
    println!("Fetching current timestamp...");
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_secs()
}

#[cached(
    time = 300,  // 5分間キャッシュ
    key = "String", 
    convert = r#"{ format!("{}", url) }"#
)]
fn fetch_api_data(url: &str) -> String {
    println!("Fetching data from: {}", url);
    // 実際のAPIリクエスト処理
    format!("Data from {}", url)
}

サイズ制限付きキャッシュ

use cached::proc_macro::cached;
use cached::SizedCache;

// 最大100エントリまでキャッシュ
#[cached(
    type = "SizedCache<String, String>",
    create = "{ SizedCache::with_size(100) }",
    convert = r#"{ format!("{}", query) }"#
)]
fn search_database(query: &str) -> String {
    println!("Searching database for: {}", query);
    // データベース検索処理
    format!("Results for {}", query)
}

高度な使い方

非同期関数のキャッシング

use cached::proc_macro::cached;
use cached::AsyncRedisCache;

// メモリキャッシュを使用した非同期関数
#[cached(time = 60, result = true)]
async fn fetch_user_data(user_id: u64) -> Result<User, Error> {
    println!("Fetching user {} from API...", user_id);
    // 非同期API呼び出し
    let response = reqwest::get(&format!("https://api.example.com/users/{}", user_id))
        .await?
        .json::<User>()
        .await?;
    Ok(response)
}

// Redisバックエンドを使用した非同期キャッシュ
#[cached(
    type = "AsyncRedisCache<String, String>",
    create = r#"{
        AsyncRedisCache::new("cached_prefix", 60)
            .set_namespace("my_app")
            .build()
            .await
            .expect("error building redis cache")
    }"#,
    convert = r#"{ format!("{}", user_id) }"#
)]
async fn fetch_user_profile(user_id: u64) -> String {
    println!("Fetching profile for user {}", user_id);
    // プロフィール取得処理
    format!("Profile data for user {}", user_id)
}

#[derive(serde::Deserialize)]
struct User {
    id: u64,
    name: String,
}

#[derive(Debug)]
struct Error;

impl From<reqwest::Error> for Error {
    fn from(_: reqwest::Error) -> Self {
        Error
    }
}

カスタムキャッシュ実装

use cached::{Cached, UnboundCache};
use std::collections::HashMap;
use std::sync::Mutex;

// カスタムキャッシュストアの作成
struct CustomCache {
    store: Mutex<UnboundCache<String, String>>,
    hits: Mutex<u64>,
    misses: Mutex<u64>,
}

impl CustomCache {
    fn new() -> Self {
        Self {
            store: Mutex::new(UnboundCache::new()),
            hits: Mutex::new(0),
            misses: Mutex::new(0),
        }
    }
    
    fn get_stats(&self) -> (u64, u64) {
        let hits = *self.hits.lock().unwrap();
        let misses = *self.misses.lock().unwrap();
        (hits, misses)
    }
}

// 手動でキャッシュを使用
fn manual_cache_example() {
    let cache = CustomCache::new();
    
    // キャッシュにデータを保存
    {
        let mut store = cache.store.lock().unwrap();
        store.cache_set("key1".to_string(), "value1".to_string());
    }
    
    // キャッシュからデータを取得
    let value = {
        let mut store = cache.store.lock().unwrap();
        match store.cache_get(&"key1".to_string()) {
            Some(v) => {
                *cache.hits.lock().unwrap() += 1;
                Some(v.clone())
            }
            None => {
                *cache.misses.lock().unwrap() += 1;
                None
            }
        }
    };
    
    println!("Value: {:?}", value);
    let (hits, misses) = cache.get_stats();
    println!("Cache stats - Hits: {}, Misses: {}", hits, misses);
}

複雑なキーの処理

use cached::proc_macro::cached;
use std::hash::{Hash, Hasher};

#[derive(Clone)]
struct ComplexKey {
    id: u64,
    name: String,
    tags: Vec<String>,
}

// カスタムハッシュ実装
impl Hash for ComplexKey {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.id.hash(state);
        self.name.hash(state);
        // タグをソートしてからハッシュ(順序に依存しない)
        let mut sorted_tags = self.tags.clone();
        sorted_tags.sort();
        sorted_tags.hash(state);
    }
}

impl PartialEq for ComplexKey {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id && 
        self.name == other.name && 
        {
            let mut tags1 = self.tags.clone();
            let mut tags2 = other.tags.clone();
            tags1.sort();
            tags2.sort();
            tags1 == tags2
        }
    }
}

impl Eq for ComplexKey {}

#[cached(
    key = "ComplexKey",
    convert = r#"{ ComplexKey { id: *id, name: name.to_string(), tags: tags.to_vec() } }"#
)]
fn process_complex_data(id: &u64, name: &str, tags: &[String]) -> String {
    println!("Processing complex data...");
    format!("Processed: {} - {} - {:?}", id, name, tags)
}

実践的な例

Web APIクライアントでの使用

use cached::proc_macro::cached;
use serde::{Deserialize, Serialize};
use std::error::Error;

#[derive(Debug, Clone, Serialize, Deserialize)]
struct WeatherData {
    temperature: f64,
    humidity: f64,
    description: String,
}

// 天気データを10分間キャッシュ
#[cached(
    time = 600,
    result = true,
    key = "String",
    convert = r#"{ format!("{}:{}", city, country) }"#
)]
async fn get_weather(city: &str, country: &str) -> Result<WeatherData, Box<dyn Error>> {
    println!("Fetching weather for {}, {}", city, country);
    
    let url = format!(
        "https://api.weather.example.com/current?city={}&country={}",
        city, country
    );
    
    let response = reqwest::get(&url).await?;
    let data = response.json::<WeatherData>().await?;
    
    Ok(data)
}

// 使用例
async fn weather_service() {
    // 初回はAPIを呼び出す
    match get_weather("Tokyo", "JP").await {
        Ok(weather) => println!("Weather: {:?}", weather),
        Err(e) => eprintln!("Error: {}", e),
    }
    
    // 2回目はキャッシュから返される
    match get_weather("Tokyo", "JP").await {
        Ok(weather) => println!("Weather (cached): {:?}", weather),
        Err(e) => eprintln!("Error: {}", e),
    }
}

データベースクエリのキャッシング

use cached::proc_macro::cached;
use cached::TimedSizedCache;

#[derive(Clone, Debug)]
struct User {
    id: u64,
    username: String,
    email: String,
}

// 最大1000エントリ、1時間キャッシュ
#[cached(
    type = "TimedSizedCache<u64, Option<User>>",
    create = "{ TimedSizedCache::with_size_and_lifespan(1000, 3600) }",
    convert = r#"{ *user_id }"#
)]
fn get_user_by_id(user_id: &u64) -> Option<User> {
    println!("Querying database for user {}", user_id);
    
    // データベースクエリのシミュレーション
    if *user_id < 1000 {
        Some(User {
            id: *user_id,
            username: format!("user{}", user_id),
            email: format!("user{}@example.com", user_id),
        })
    } else {
        None
    }
}

// バッチ処理での使用
fn process_users(user_ids: Vec<u64>) {
    for id in user_ids {
        match get_user_by_id(&id) {
            Some(user) => println!("Processing user: {:?}", user),
            None => println!("User {} not found", id),
        }
    }
}

計算集約的なタスクの最適化

use cached::proc_macro::cached;
use rayon::prelude::*;

// 重い計算をキャッシュ
#[cached(size = 10000)]
fn expensive_computation(input: u64) -> u128 {
    println!("Computing for input: {}", input);
    
    // 素因数分解のシミュレーション
    let mut n = input;
    let mut factors = Vec::new();
    let mut d = 2;
    
    while d * d <= n {
        while n % d == 0 {
            factors.push(d);
            n /= d;
        }
        d += 1;
    }
    
    if n > 1 {
        factors.push(n);
    }
    
    // 結果を返す(例:因数の積)
    factors.iter().map(|&f| f as u128).product()
}

// 並列処理での使用
fn parallel_computation(inputs: Vec<u64>) -> Vec<u128> {
    inputs
        .par_iter()
        .map(|&input| expensive_computation(input))
        .collect()
}

パフォーマンスのベストプラクティス

1. 適切なキャッシュタイプの選択

use cached::proc_macro::cached;
use cached::{UnboundCache, SizedCache, TimedCache, TimedSizedCache};

// 無制限キャッシュ(メモリに注意)
#[cached]
fn unlimited_cache(key: String) -> String {
    format!("Processing {}", key)
}

// サイズ制限(LRU)
#[cached(size = 1000)]
fn sized_cache(key: String) -> String {
    format!("Processing {}", key)
}

// 時間制限
#[cached(time = 300)]
fn timed_cache(key: String) -> String {
    format!("Processing {}", key)
}

// サイズと時間の両方を制限
#[cached(size = 1000, time = 300)]
fn timed_sized_cache(key: String) -> String {
    format!("Processing {}", key)
}

2. キーの最適化

use cached::proc_macro::cached;

// 効率的なキー変換
#[cached(
    key = "String",
    convert = r#"{ format!("{}:{}", a, b) }"#  // シンプルで高速
)]
fn optimized_key(a: u32, b: u32) -> u64 {
    (a as u64) * (b as u64)
}

// 複雑な構造体の場合はハッシュを使用
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

#[cached(
    key = "u64",
    convert = r#"{
        let mut hasher = DefaultHasher::new();
        data.hash(&mut hasher);
        hasher.finish()
    }"#
)]
fn hash_based_key(data: &ComplexData) -> String {
    format!("Processed: {:?}", data)
}

#[derive(Hash, Debug)]
struct ComplexData {
    values: Vec<i32>,
    metadata: String,
}

3. エラーハンドリング

use cached::proc_macro::cached;

// 成功した結果のみキャッシュ
#[cached(result = true)]
fn may_fail(input: u32) -> Result<String, String> {
    if input % 2 == 0 {
        Ok(format!("Success: {}", input))
    } else {
        Err(format!("Error: {} is odd", input))
    }
}

// カスタムエラー処理
#[cached(
    result = true,
    with_cached_flag = true
)]
fn with_cache_info(input: u32) -> Result<(String, bool), String> {
    // boolはキャッシュからの取得かどうかを示す
    Ok((format!("Value: {}", input), false))
}

他のキャッシュライブラリとの比較

cached vs moka

特徴cachedmoka
マクロサポート×
非同期対応
キャッシュアルゴリズムLRUTinyLFU
Redis統合×
使いやすさ

cached vs lru

特徴cachedlru
機能の豊富さ
パフォーマンス
時間ベース削除×
マクロサポート×

まとめ

cachedは、Rustアプリケーションにキャッシュ機能を簡単に追加できる強力なライブラリです。プロシージャルマクロによる宣言的なアプローチにより、既存のコードに最小限の変更でキャッシュを導入できます。多様なキャッシュバックエンドとカスタマイズオプションにより、さまざまなユースケースに対応可能です。