bind-macro
Binds trait objects to concrete implementations using bind!(). Use when injecting dyn Trait types, swapping implementations (mock vs production), or getting 'conflicting implementations' errors.
SKILL.mdskills/bind-macro/SKILL.md
GitHubbind! Macro
Creates a static binding from a trait to a concrete type for Inject<dyn Trait>.
Use it with #[injectable(trait)] when you want the trait itself to opt into
the injectable docs/discovery flow.
Basic pattern
use injectable::prelude::*;
// Define the trait
#[injectable(trait)]
pub trait EmailSender {
fn send(&self, to: &str, subject: &str, body: &str);
}
// Implement the trait
pub struct SmtpSender;
impl EmailSender for SmtpSender {
fn send(&self, to: &str, subject: &str, body: &str) {
println!("Sending email to {to}: {subject}");
}
}
// Create a singleton binding
bind!(dyn EmailSender => SmtpSender);
// Now Inject<dyn EmailSender> works
#[injectable]
struct NotificationService {
sender: Inject<dyn EmailSender>,
}
With DynProvider for external traits
use injectable::prelude::*;
#[injectable(trait)]
pub trait Cache {
fn get(&self, key: &str) -> Option<String>;
fn set(&self, key: &str, value: String);
}
pub struct InMemoryCache {
store: std::collections::HashMap<String, String>,
}
impl Cache for InMemoryCache {
fn get(&self, key: &str) -> Option<String> {
self.store.get(key).cloned()
}
fn set(&self, key: &str, value: String) {
self.store.insert(key, value);
}
}
bind!(dyn Cache => InMemoryCache);
#[injectable]
struct UserService {
cache: Inject<dyn Cache>,
}
Mock 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 in test
bind!(dyn EmailSender => MockEmailSender);
bind! vs manual registration
| Approach | Use when |
|---|---|
bind!(dyn Trait => Concrete) |
Concrete type is #[injectable] |
DynProvider |
External type or needs custom factory |
| Both combined | Trait from external crate + custom factory |
The bind! macro generates the Extract impl for Inject<dyn Trait> that delegates to Concrete::Provider.
Notes:
bind!resolves through the concrete provider and does not go through the singleton cache forInject<dyn Trait>.- The trait annotation is recommended for clarity, but
bind!can still work with unannotated traits when the trait object is otherwise valid.
See skills/injectable-trait, guides/04-external-types.md, and guides/README.md.