rust-async-trait-cache

test12026-10-2513 次阅读

遇到的问题

想在 trait 里写 async fn,再包成 Box 做依赖注入,直接用原生 async trait 在 trait 对象上会编译不过。async-trait 这个 crate 把 async fn 在编译期改写成返回 Future 的普通方法,从而支持 dyn 调用。

定义异步 trait

给 trait 标上 #[async_trait],里面就能正常写 async fn。

use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::Mutex;
use std::time::{Duration, Instant};

#[async_trait]
trait Cache {
    async fn get(&self, key: &str) -> Option<String>;
    async fn set(&self, key: String, value: String, ttl: Duration);
}

实现带 TTL 的内存缓存

存储用 Mutex 保护的 HashMap,值里额外记录过期时间点。读的时候顺手做一次过期判断。

struct MemoryCache {
    store: Mutex<HashMap<String, (String, Instant)>>,
}

#[async_trait]
impl Cache for MemoryCache {
    async fn get(&self, key: &str) -> Option<String> {
        let store = self.store.lock().unwrap();
        store.get(key).and_then(|(v, exp)| {
            if *exp > Instant::now() {
                Some(v.clone())
            } else {
                None
            }
        })
    }

    async fn set(&self, key: String, value: String, ttl: Duration) {
        let mut store = self.store.lock().unwrap();
        store.insert(key, (value, Instant::now() + ttl));
    }
}

用 Box 做可替换抽象

业务代码只依赖 Cache trait,测试时换成假实现、上线时换成 Redis 实现,调用方一行都不用改。

#[tokio::main]
async fn main() {
    let cache: Box<dyn Cache> = Box::new(MemoryCache {
        store: Mutex::new(HashMap::new()),
    });

    cache
        .set("k".into(), "v".into(), Duration::from_secs(60))
        .await;
    println!("{:?}", cache.get("k").await);
}

什么时候用

  • 需要在运行时切换实现(内存、Redis、测试桩)时,dyn trait 对象很合适。
  • 单实现、编译期确定类型时,直接泛型 更省一次动态分发。
  • 原生 async fn in trait 已稳定,但 trait 对象(dyn)的异步调用仍常用 async-trait 来保证可用性,等语言彻底补齐再移除也不迟。
T

test1

文章作者

遇到的问题 想在 trait 里写 async fn,再包成 Box 做依赖注入,直接用原生 async trait...

分类
技术
发布时间
2026-10-25
字数
约 1700 字
阅读
13 次