Loading pageā¦
Rust walkthroughs
Loading pageā¦
Rust doesn't natively support async functions in traits (though this is changing with recent Rust versions). The async-trait crate provides a procedural macro that makes this possible by boxing futures. It's essential for building async abstractions and polymorphic async code.
Why async-trait matters:
Pin<Box<dyn Future>>Note: Rust 1.75+ supports native async trait methods (RPITIT), but async-trait remains useful for compatibility with older Rust versions and for certain advanced patterns.
# Cargo.toml
[dependencies]
async-trait = "0.1"
tokio = { version = "1", features = ["full"] }use async_trait::async_trait;
use std::error::Error;
// Define an async trait using the macro
#[async_trait]
pub trait Database {
async fn connect(&mut self, connection_string: &str) -> Result<(), Box<dyn Error>>;
async fn query(&self, sql: &str) -> Result<Vec<String>, Box<dyn Error>>;
async fn disconnect(&mut self) -> Result<(), Box<dyn Error>>;
}
// Implement the trait for a PostgreSQL-like database
struct PostgresDatabase {
connected: bool,
}
#[async_trait]
impl Database for PostgresDatabase {
async fn connect(&mut self, connection_string: &str) -> Result<(), Box<dyn Error>> {
println!("Connecting to PostgreSQL: {}", connection_string);
// Simulate async connection
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
self.connected = true;
Ok(())
}
async fn query(&self, sql: &str) -> Result<Vec<String>, Box<dyn Error>> {
if !self.connected {
return Err("Not connected".into());
}
println!("Executing query: {}", sql);
Ok(vec![
"row1".to_string(),
"row2".to_string(),
])
}
async fn disconnect(&mut self) -> Result<(), Box<dyn Error>> {
println!("Disconnecting from PostgreSQL");
self.connected = false;
Ok(())
}
}
// Implement the trait for a mock database (useful for testing)
struct MockDatabase {
records: Vec<String>,
}
#[async_trait]
impl Database for MockDatabase {
async fn connect(&mut self, _connection_string: &str) -> Result<(), Box<dyn Error>> {
Ok(())
}
async fn query(&self, _sql: &str) -> Result<Vec<String>, Box<dyn Error>> {
Ok(self.records.clone())
}
async fn disconnect(&mut self) -> Result<(), Box<dyn Error>> {
Ok(())
}
}
// Function that works with any type implementing Database
async fn fetch_users(db: &dyn Database) -> Result<Vec<String>, Box<dyn Error>> {
let users = db.query("SELECT * FROM users").await?;
Ok(users)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Use PostgresDatabase
let mut postgres = PostgresDatabase { connected: false };
postgres.connect("postgresql://localhost/mydb").await?;
let users = fetch_users(&postgres).await?;
println!("Users: {:?}", users);
postgres.disconnect().await?;
// Use MockDatabase for testing
let mock = MockDatabase {
records: vec!["test_user".to_string()],
};
let test_users = fetch_users(&mock).await?;
println!("Test users: {:?}", test_users);
Ok(())
}use async_trait::async_trait;
#[async_trait]
pub trait Cache<K, V> {
async fn get(&self, key: &K) -> Option<V>;
async fn set(&mut self, key: K, value: V) -> bool;
async fn remove(&mut self, key: &K) -> Option<V>;
}
struct InMemoryCache<K, V> {
data: std::collections::HashMap<K, V>,
}
#[async_trait]
impl<K, V> Cache<K, V> for InMemoryCache<K, V>
where
K: std::hash::Hash + Eq + Clone + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
async fn get(&self, key: &K) -> Option<V> {
self.data.get(key).cloned()
}
async fn set(&mut self, key: K, value: V) -> bool {
self.data.insert(key, value).is_some()
}
async fn remove(&mut self, key: &K) -> Option<V> {
self.data.remove(key)
}
}#[async_trait] attribute to both trait definition and impl blocksPin<Box<dyn Future>> behind the scenesSend bounds for types used across await points (the macro handles this by default)