--- description: Modernize async trait usage to native async fn in traits --- You are helping modernize async trait definitions to use native async fn in traits instead of the async-trait crate. ## Your Task Convert traits using the `async-trait` crate to native async fn in traits, which has been supported since Rust 1.75. ## Background **Since Rust 1.75 (December 2023):** Async functions in traits are natively supported without requiring the `async-trait` crate. **When to use async-trait:** - Supporting Rust versions < 1.75 - Need for `dyn Trait` (dynamic dispatch/object safety) - Specific edge cases with complex bounds **When to use native async fn:** - Rust 1.75 or later ✅ - Static dispatch (generics) - Modern codebases ## Steps 1. **Check Current Usage** Scan for async-trait usage: ```rust use async_trait::async_trait; #[async_trait] trait MyTrait { async fn method(&self) -> Result; } ``` 2. **Verify Rust Version** Check Cargo.toml: ```toml [package] rust-version = "1.75" # Or higher ``` If rust-version < 1.75, ask user if they can upgrade. 3. **Identify Use Cases** Categorize each async trait: **Can Remove async-trait (most common):** - Trait used with generics/static dispatch - No `Box` usage - Rust 1.75+ **Must Keep async-trait:** - Using `dyn Trait` for dynamic dispatch - Supporting older Rust versions - Object safety required 4. **Convert to Native Async Fn** **Before:** ```rust use async_trait::async_trait; #[async_trait] pub trait UserRepository: Send + Sync { async fn find_user(&self, id: &str) -> Result; async fn save_user(&self, user: &User) -> Result<(), Error>; async fn delete_user(&self, id: &str) -> Result<(), Error>; } #[async_trait] impl UserRepository for PostgresRepository { async fn find_user(&self, id: &str) -> Result { sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id) .fetch_one(&self.pool) .await } async fn save_user(&self, user: &User) -> Result<(), Error> { sqlx::query!( "INSERT INTO users (id, email) VALUES ($1, $2)", user.id, user.email ) .execute(&self.pool) .await?; Ok(()) } async fn delete_user(&self, id: &str) -> Result<(), Error> { sqlx::query!("DELETE FROM users WHERE id = $1", id) .execute(&self.pool) .await?; Ok(()) } } ``` **After:** ```rust // No async_trait import needed! pub trait UserRepository: Send + Sync { async fn find_user(&self, id: &str) -> Result; async fn save_user(&self, user: &User) -> Result<(), Error>; async fn delete_user(&self, id: &str) -> Result<(), Error>; } impl UserRepository for PostgresRepository { async fn find_user(&self, id: &str) -> Result { sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id) .fetch_one(&self.pool) .await } async fn save_user(&self, user: &User) -> Result<(), Error> { sqlx::query!( "INSERT INTO users (id, email) VALUES ($1, $2)", user.id, user.email ) .execute(&self.pool) .await?; Ok(()) } async fn delete_user(&self, id: &str) -> Result<(), Error> { sqlx::query!("DELETE FROM users WHERE id = $1", id) .execute(&self.pool) .await?; Ok(()) } } ``` 5. **Handle Dynamic Dispatch Cases** If you need `dyn Trait`, keep async-trait: ```rust // When you need this: let repo: Box = Box::new(PostgresRepository::new(pool)); // You MUST use async-trait for object safety: use async_trait::async_trait; #[async_trait] pub trait UserRepository: Send + Sync { async fn find_user(&self, id: &str) -> Result; } ``` Or redesign to avoid dynamic dispatch: ```rust // Alternative: Use generics instead pub async fn process_users(repo: R) { // Works with any UserRepository implementation let user = repo.find_user("123").await.unwrap(); } ``` 6. **Remove async-trait Dependency** Update Cargo.toml: **Before:** ```toml [dependencies] async-trait = "0.1" ``` **After:** ```toml [dependencies] # async-trait removed - using native async fn in traits ``` If still needed for some traits: ```toml [dependencies] # Only needed for dyn Trait support async-trait = "0.1" # Optional, only for object-safe traits ``` 7. **Update Imports** Remove unused async-trait imports: ```rust // Remove this if no longer needed use async_trait::async_trait; ``` 8. **Run Tests** Verify everything compiles and works: ```bash cargo check cargo test cargo clippy ``` 9. **Provide Summary** ``` ✅ Modernized async trait usage! ## Changes Made: ### Converted to Native Async Fn (3 traits): - UserRepository (src/domain/user.rs) - OrderRepository (src/domain/order.rs) - PaymentGateway (src/ports/payment.rs) ### Kept async-trait (1 trait): - DynamicHandler (src/handlers/mod.rs) Reason: Uses Box for plugin system ## Dependency Updates: - Removed async-trait from main dependencies - Added as optional for dynamic dispatch cases ## Benefits: - ✅ Zero-cost abstraction (no boxing overhead) - ✅ Simpler code (no macro needed) - ✅ Better error messages - ✅ Native language feature ## Before/After Example: Before: ```rust use async_trait::async_trait; #[async_trait] trait Repository { async fn find(&self, id: &str) -> Result; } #[async_trait] impl Repository for MyRepo { async fn find(&self, id: &str) -> Result { // ... } } ``` After: ```rust // No import needed! trait Repository { async fn find(&self, id: &str) -> Result; } impl Repository for MyRepo { async fn find(&self, id: &str) -> Result { // ... } } ``` ## Next Steps: 1. All tests passing ✅ 2. Consider removing async-trait entirely if unused 3. Update documentation ``` ## Key Differences ### Native Async Fn (Rust 1.75+) **Pros:** - No external dependency - Zero-cost abstraction - Better compiler errors - Simpler syntax - Native language feature **Cons:** - Cannot use with `dyn Trait` directly - Requires Rust 1.75+ **Usage:** ```rust trait MyTrait { async fn method(&self) -> Result; } // Use with generics fn process(t: T) { } ``` ### Async-Trait Crate **Pros:** - Works with older Rust - Supports `dyn Trait` - Object-safe traits **Cons:** - External dependency - Macro overhead - Slight performance cost (boxing) **Usage:** ```rust use async_trait::async_trait; #[async_trait] trait MyTrait { async fn method(&self) -> Result; } // Can use with dyn let t: Box = Box::new(impl); ``` ## Migration Patterns ### Pattern 1: Simple Repository ```rust // Before #[async_trait] trait Repository { async fn get(&self, id: i32) -> Option; } // After trait Repository { async fn get(&self, id: i32) -> Option; } ``` ### Pattern 2: Generic Service ```rust // Before #[async_trait] trait Service { async fn process(&self, item: T) -> Result<(), Error>; } // After trait Service { async fn process(&self, item: T) -> Result<(), Error>; } ``` ### Pattern 3: Multiple Async Methods ```rust // Before #[async_trait] trait Complex { async fn fetch(&self) -> Result; async fn save(&self, data: Data) -> Result<(), Error>; async fn delete(&self, id: i32) -> Result<(), Error>; } // After - just remove the macro! trait Complex { async fn fetch(&self) -> Result; async fn save(&self, data: Data) -> Result<(), Error>; async fn delete(&self, id: i32) -> Result<(), Error>; } ``` ### Pattern 4: Keep for Dynamic Dispatch ```rust // When you need this: struct PluginSystem { plugins: Vec>, } // Keep async-trait: #[async_trait] trait Plugin: Send + Sync { async fn execute(&self) -> Result<(), Error>; } ``` ## Important Notes - Native async fn in traits requires **Rust 1.75+** - Check MSRV before removing async-trait - `dyn Trait` requires async-trait (or alternatives) - Static dispatch (generics) works with native async fn - Performance is better with native async fn (no boxing) ## Version Requirements | Feature | Rust Version | Notes | |---------|-------------|-------| | Async fn in traits | 1.75.0+ | Native support | | async-trait crate | Any | Fallback for older versions | | Return-position impl Trait | 1.75.0+ | Enables async fn | ## After Completion Ask the user: 1. Did all tests pass? 2. Can we remove async-trait entirely? 3. Are there any dyn Trait use cases remaining? 4. Should we update documentation?