Loading page…
Rust walkthroughs
Loading page…
Rust doesn't natively support async functions in traits (though this is changing with recent versions). The async-trait crate provides a macro that enables async methods in trait definitions. It works by boxing the futures returned by async methods, making them dynamically dispatched.
Why you need async-trait:
async fn in traits returns impl Future, which has an anonymous typeasync fn to return Pin<Box<dyn Future>>, which has a known sizeNote: Rust 1.75+ supports native async fn in traits for some cases, but async-trait remains useful for object-safe traits and complex scenarios.
# Cargo.toml
[dependencies]
async-trait = "0.1"
tokio = { version = "1", features = ["full"] }use async_trait::async_trait;
use std::error::Error;
// ===== Basic Async Trait =====
#[async_trait]
pub trait Database {
async fn get_user(&self, id: u64) -> Option<String>;
async fn save_user(&self, id: u64, name: &str) -> Result<(), Box<dyn Error>>;
}
// ===== Implementation for Concrete Type =====
struct InMemoryDb {
users: std::collections::HashMap<u64, String>,
}
impl InMemoryDb {
fn new() -> Self {
let mut users = std::collections::HashMap::new();
users.insert(1, "Alice".to_string());
users.insert(2, "Bob".to_string());
Self { users }
}
}
#[async_trait]
impl Database for InMemoryDb {
async fn get_user(&self, id: u64) -> Option<String> {
// Simulate async work
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
self.users.get(&id).cloned()
}
async fn save_user(&self, id: u64, name: &str) -> Result<(), Box<dyn Error>> {
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
self.users.insert(id, name.to_string());
Ok(())
}
}
// ===== Multiple Implementations =====
struct FileDb {
path: String,
}
#[async_trait]
impl Database for FileDb {
async fn get_user(&self, id: u64) -> Option<String> {
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
// Simulated file read
println!("Reading user {} from {}", id, self.path);
Some(format!("user_{}", id))
}
async fn save_user(&self, id: u64, name: &str) -> Result<(), Box<dyn Error>> {
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
println!("Saving user {}={} to {}", id, name, self.path);
Ok(())
}
}
// ===== Using Trait Objects (dyn Trait) =====
async fn fetch_user(db: &dyn Database, id: u64) -> Option<String> {
db.get_user(id).await
}
async fn process_users(databases: &[Box<dyn Database>]) {
for (i, db) in databases.iter().enumerate() {
if let Some(user) = db.get_user(1).await {
println!("Database {} found user: {}", i, user);
}
}
}
// ===== Async Trait with Self Bounds =====
#[async_trait]
pub trait Cache: Send + Sync {
async fn get(&self, key: &str) -> Option<String>;
async fn set(&self, key: &str, value: &str);
async fn clear(&self);
}
struct RedisCache {
url: String,
}
#[async_trait]
impl Cache for RedisCache {
async fn get(&self, key: &str) -> Option<String> {
println!("GET {} from Redis at {}", key, self.url);
None
}
async fn set(&self, key: &str, value: &str) {
println!("SET {}={} in Redis at {}", key, value, self.url);
}
async fn clear(&self) {
println!("CLEAR all keys in Redis at {}", self.url);
}
}
// ===== Async Trait with Generics =====
#[async_trait]
pub trait Repository<T> {
async fn find_by_id(&self, id: u64) -> Option<T>;
async fn save(&self, entity: T) -> Result<(), Box<dyn Error>>;
async fn delete(&self, id: u64) -> bool;
}
struct User {
id: u64,
name: String,
}
struct UserRepository {
cache: Box<dyn Cache>,
}
#[async_trait]
impl Repository<User> for UserRepository {
async fn find_by_id(&self, id: u64) -> Option<User> {
let cached = self.cache.get(&format!("user:{}", id)).await;
cached.map(|name| User { id, name })
}
async fn save(&self, entity: User) -> Result<(), Box<dyn Error>> {
self.cache.set(&format!("user:{}", entity.id), &entity.name).await;
Ok(())
}
async fn delete(&self, id: u64) -> bool {
println!("Deleting user {}", id);
true
}
}
// ===== Real-World Pattern: Plugin System =====
#[async_trait]
pub trait Plugin {
fn name(&self) -> &str;
async fn initialize(&mut self) -> Result<(), Box<dyn Error>>;
async fn execute(&self, input: &str) -> Result<String, Box<dyn Error>>;
async fn shutdown(&mut self) -> Result<(), Box<dyn Error>>;
}
struct LoggingPlugin;
#[async_trait]
impl Plugin for LoggingPlugin {
fn name(&self) -> &str {
"LoggingPlugin"
}
async fn initialize(&mut self) -> Result<(), Box<dyn Error>> {
println!("Initializing {}", self.name());
Ok(())
}
async fn execute(&self, input: &str) -> Result<String, Box<dyn Error>> {
println!("[LOG] {}", input);
Ok(format!("Logged: {}", input))
}
async fn shutdown(&mut self) -> Result<(), Box<dyn Error>> {
println!("Shutting down {}", self.name());
Ok(())
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Use concrete implementation
let db = InMemoryDb::new();
if let Some(user) = db.get_user(1).await {
println!("Found user: {}", user);
}
db.save_user(3, "Charlie").await?;
println!("Saved user 3");
// Use through trait object
let db: Box<dyn Database> = Box::new(InMemoryDb::new());
let user = fetch_user(&*db, 2).await;
println!("Fetched via trait object: {:?}", user);
// Multiple implementations
let databases: Vec<Box<dyn Database>> = vec![
Box::new(InMemoryDb::new()),
Box::new(FileDb { path: "/data/db.json".to_string() }),
];
process_users(&databases).await;
// Plugin system
let mut plugins: Vec<Box<dyn Plugin>> = vec![
Box::new(LoggingPlugin),
];
for plugin in &mut plugins {
plugin.initialize().await?;
}
for plugin in &plugins {
let result = plugin.execute("Hello, World!").await?;
println!("Result: {}", result);
}
for plugin in &mut plugins {
plugin.shutdown().await?;
}
Ok(())
}use async_trait::async_trait;
#[async_trait]
pub trait AsyncProcessor {
async fn process(&self, data: &str) -> String;
// Default implementation calling process
async fn process_batch(&self, items: &[&str]) -> Vec<String> {
let mut results = Vec::new();
for item in items {
results.push(self.process(item).await);
}
results
}
}
struct UppercaseProcessor;
#[async_trait]
impl AsyncProcessor for UppercaseProcessor {
async fn process(&self, data: &str) -> String {
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
data.to_uppercase()
}
}
// Usage
async fn demo() {
let processor = UppercaseProcessor;
let results = processor.process_batch(&["hello", "world"]).await;
println!("{:?}", results); // ["HELLO", "WORLD"]
}#[async_trait] to both the trait definition and its implementationsPin<Box<dyn Future>> under the hood, enabling dynamic dispatch&dyn Trait or Box<dyn Trait> to store and pass around trait objects with async methodsSend bound for use across threads (add : Send + Sync to trait bounds)trait Repository<T> { ... }&self