Dependency Inversion with #[contract]
What Is Dependency Inversion?
Dependency inversion is a concept that refers to the practice of making code depend on abstractions (traits), not on concrete implementations (specific structs).
An everyday example: a laptop works with any printer that follows the USB standard. The laptop doesn't care about the printer brand — it only cares about the contract (USB). You can swap printers without changing the laptop.
The same principle applies to code: instead of a controller creating a DatabaseTaskRepository directly, it depends on a TaskRepository trait. The actual implementation can change without modifying the controller.
When Should You Use It?
- You have multiple data sources (database, cache, external API) and want to switch between them.
- You want to test business logic without setting up a real database.
- You work in a team and want to separate what something does from how it does it.
- You anticipate that a technology might change in the future.
What Is a #[contract]?
A contract is a trait annotated with #[contract]. It defines what something does, not how it does it.
use sword::prelude::*;
#[contract]
pub trait TaskRepository {
async fn find_all(&self) -> Vec<Value>;
}This contract says: "anything that implements me can find all tasks." It doesn't say whether tasks come from PostgreSQL, Redis, or an in-memory list.
Contracts support both async and sync methods.
How Do You Implement a Contract?
The real implementation also uses #[contract] on the impl block:
#[contract]
impl TaskRepository for DatabaseTaskRepository {
async fn find_all(&self) -> Vec<Value> {
// real database query
todo!()
}
}You can have multiple implementations of the same contract — one for production, one for testing, one for a cache layer.
How Do You Inject a Contract?
Inject the contract as Arc<dyn Trait> in any struct that supports dependency injection.
In a Controller
#[controller(kind = Controller::Web, path = "/tasks")]
pub struct TasksController {
repo: Arc<dyn TaskRepository>,
}In a Component (#[injectable])
#[injectable]
pub struct TasksService {
repo: Arc<dyn TaskRepository>,
}In a Provider (#[injectable(provider)])
#[injectable(provider)]
pub struct TasksQueue {
repo: Arc<dyn TaskRepository>,
}The injection works the same way in all three cases — the DI container resolves the correct implementation automatically.
Full Example
Before — tightly coupled, hard to test:
pub struct TasksController {
repo: DatabaseTaskRepository,
}
impl TasksController {
#[get("/")]
async fn list(&self) -> WebResult {
let tasks = self.repo.find_all().await; // needs a real DB
Ok(JsonResponse::Ok().data(tasks))
}
}After — decoupled, easy to swap:
// 1. Define the contract
#[contract]
pub trait TaskRepository {
async fn find_all(&self) -> Vec<Value>;
}
// 2. Implement it
#[contract]
impl TaskRepository for DatabaseTaskRepository {
async fn find_all(&self) -> Vec<Value> {
todo!()
}
}
// 3. Use it in the controller
#[controller(kind = Controller::Web, path = "/tasks")]
pub struct TasksController {
repo: Arc<dyn TaskRepository>,
}
impl TasksController {
#[get("/")]
async fn list(&self) -> WebResult {
let tasks = self.repo.find_all().await;
Ok(JsonResponse::Ok().data(tasks))
}
}Now you can swap the database for a cache without touching the controller.

