injectable-trait
Makes traits injectable using
SKILL.mdskills/injectable-trait/SKILL.md
GitHubTrait Injection with #[injectable(trait)]
Generates infrastructure for trait injection, including a type-erased provider and Inject<dyn Trait> support.
Basic pattern
use injectable::prelude::*;
#[injectable(trait)]
pub trait EmailSender {
fn send(&self, to: &str, subject: &str, body: &str);
}
pub struct SmtpSender;
impl EmailSender for SmtpSender {
fn send(&self, to: &str, subject: &str, body: &str) {
println!("Sending email to {to}: {subject}");
}
}
bind!(dyn EmailSender => SmtpSender);
#[injectable]
struct NotificationService {
sender: Inject<dyn EmailSender>,
}
How it works
#[injectable(trait)]marks the trait as part of the injectable surfacebind!()creates a static binding fromdyn TraittoConcreteInject<dyn Trait>resolves via the bound concrete type's provider
With default implementation
#[injectable(trait)]
pub trait Logger {
fn log(&self, msg: &str) {
println!("[LOG] {msg}");
}
}
pub struct NoOpLogger;
impl Logger for NoOpLogger {}
bind!(dyn Logger => NoOpLogger);
Mocking in tests
pub struct MockEmailSender;
impl EmailSender for MockEmailSender {
fn send(&self, to: &str, _subject: &str, _body: &str) {
println!("[MOCK] Would send email to {to}");
}
}
// Override binding
bind!(dyn EmailSender => MockEmailSender);
Error handling
#[injectable]
struct UserService {
sender: Inject<dyn EmailSender>,
}
impl UserService {
fn notify_user(&self, user: &User) {
self.sender.send(&user.email, "Welcome!", "Hello from injectable!");
}
}
Guidelines
- Use
#[injectable(trait)]on traits that have a single primary implementation - Use
bind!()to declare which concrete type satisfies the trait Inject<dyn Trait>needs abind!()mapping; the trait annotation is the recommended documented pattern
See skills/bind-macro and guides/README.md.
This pattern enables dependency inversion in your DI graph — depend on abstractions (traits), not concrete types.