Skip to content

Dependency Injection

Dependency Injection

Rusta provides a DI container for managing service dependencies.

Basic Injection

Mark structs as injectable and declare dependencies:

use rusta::prelude::*;
use std::sync::Arc;
#[injectable]
pub struct UserService {
#[inject]
repo: Arc<dyn UserRepository>,
}
#[injectable]
pub struct UserController {
#[inject]
svc: Arc<UserService>,
}

Registration

Register services in the container:

let mut container = Container::new();
// Register repository
container.register(Arc::new(MongoUserRepository::new()));
// Register service (dependencies resolved automatically)
container.register(UserService::construct(&container));
// Register controller
container.register(UserController::construct(&container));

Optional Injection

Make dependencies optional - field becomes None if not registered:

#[injectable]
pub struct EmailService {
#[inject(optional)]
smtp_client: Option<Arc<dyn SmtpClient>>,
}
impl EmailService {
pub fn send(&self, email: Email) -> Result<(), Error> {
match &self.smtp_client {
Some(client) => client.send(email).await,
None => Err(Error::NotConfigured),
}
}
}

Named Bindings

Register multiple implementations of the same trait:

// Define two cache implementations
container.register_named(Arc::new(RedisCache::new()), "redis");
container.register_named(Arc::new(MemoryCache::new()), "memory");
// Inject by name
#[injectable]
pub struct CacheService {
#[inject(name = "redis")]
primary: Arc<dyn Cache>,
#[inject(name = "memory")]
fallback: Arc<dyn Cache>,
}

Resolution in Handlers

Use the Inject extractor to resolve dependencies in handlers:

#[get("/users")]
pub async fn list_users(
Inject(svc): Inject<Arc<UserService>>,
) -> Response {
Http::json(svc.list().await)
}

Binding Verification

Verify all required bindings at startup:

let errors = container.verify();
if !errors.is_empty() {
panic!("Missing bindings: {:?}", errors);
}

This catches missing dependencies before the server starts.

Trait Objects

Use trait objects for abstraction:

#[async_trait]
pub trait UserRepository: Send + Sync {
async fn find(&self, id: &str) -> Option<User>;
async fn save(&self, user: User) -> Result<User, Error>;
}
#[injectable]
pub struct UserService {
#[inject]
repo: Arc<dyn UserRepository>,
}

Container Reference

For middleware that needs access to the container:

async fn auth_middleware(
State(container): State<ContainerRef>,
request: Request,
next: Next,
) -> Response {
let auth_svc: Arc<AuthService> = container.resolve();
// ...
}