dyn Trait 注册与解析
核心规则:注册类型 = 解析类型
rust-dix 使用 Rust 的 `TypeId` 作为服务标识。注册时的类型必须与解析时的类型一致。
正确示例
use rust_dix::*;
use std::sync::Arc;
// 定义 trait
trait ILogger: Send + Sync {
fn log(&self, msg: &str);
}
// 定义实现
struct FileLogger;
impl ILogger for FileLogger {
fn log(&self, msg: &str) {
println!("[FILE] {}", msg);
}
}
// 注册为 dyn Trait
let provider = ServiceCollection::new()
.singleton::<dyn ILogger>(|_| Arc::new(FileLogger))
.build()
.unwrap();
// 解析为 dyn Trait
let logger: Arc<dyn ILogger> = provider.get().unwrap();
logger.log("hello");
错误示例
// ❌ 注册为具体类型
let provider = ServiceCollection::new()
.singleton(|_| Arc::new(FileLogger)) // TypeId = FileLogger
.build()
.unwrap();
// ❌ 解析为 trait
let logger: Arc<dyn ILogger> = provider.get().unwrap(); // Err: TypeId 不匹配
注册语法详解
手动注册
ServiceCollection::new()
// Singleton
.singleton::<dyn Trait>(|_| Arc::new(Impl))
// Scoped
.scoped::<dyn Trait>(|_| Arc::new(Impl))
// Transient
.transient::<dyn Trait>(|_| Arc::new(Impl))
// Keyed
.keyed_singleton::<dyn Trait>("name", |_| Arc::new(Impl))
.build()
.unwrap();
过程宏自动注册
`#[inject]` 放置位置二选一:面向接口时 struct 上用 `#[derive(Inject)]`(仅生成构造函数、不注册),impl 上用 `#[inject]` 注册为 `dyn Trait`。**禁止** struct 与 impl 同时用 `#[inject]`(会双重注册,详见 [用法指南](../08-best-practices/usage-guide.md#放置位置struct-还是-impl二选一避免双重注册))。
// struct 上用 #[derive(Inject)] 仅生成构造函数(不注册),impl 上用 #[inject] 注册为 dyn ILogger
#[derive(Inject)]
struct FileLogger;
#[inject]
impl ILogger for FileLogger {
fn log(&self, msg: &str) { println!("[FILE] {}", msg); }
}
// 使用 from_injected() 自动收集
let provider = ServiceCollection::from_injected()
.build()
.unwrap();
let logger: Arc<dyn ILogger> = provider.get().unwrap();
多接口注册
一个实现注册为多个 trait:
trait IReader: Send + Sync { fn read(&self) -> String; }
trait IWriter: Send + Sync { fn write(&self, data: &str); }
// struct 上用 #[derive(Inject)] 仅生成构造函数(不注册),两个 impl 块各注册一个 trait
#[derive(Inject)]
struct FileStream {
path: String,
}
#[inject]
impl IReader for FileStream { fn read(&self) -> String { "data" } }
#[inject]
impl IWriter for FileStream { fn write(&self, data: &str) { println!("{}", data); } }
// 分别解析
let reader: Arc<dyn IReader> = provider.get().unwrap();
let writer: Arc<dyn IWriter> = provider.get().unwrap();
解析 API
必需解析(`Result`)
// get() 返回 Result — 未注册时 Err(ServiceNotFound),不 panic
let logger: Arc<dyn ILogger> = provider.get()?;
// 或: provider.get().expect("ILogger registered at composition root");
可选解析
// try_get()
let logger: Option<Arc<dyn ILogger>> = provider.try_get();
if let Some(log) = logger {
log.log("hello");
}
键控解析
// get_keyed()
let wechat: Arc<dyn IPayGateway> = provider.get_keyed("wechat").unwrap();
// try_get_keyed()
let maybe: Option<Arc<dyn IPayGateway>> = provider.try_get_keyed("unknown");
批量解析
// get_all() / get_services()
let all: Vec<Arc<dyn ILogger>> = provider.get_all();
for logger in &all {
logger.log("broadcast");
}
IServiceResolver trait
`IServiceResolver` 是核心解析 trait,由 `ServiceProvider`、`Scope`、`ServiceProviderWrapper` 实现:
pub trait IServiceResolver: Send + Sync {
fn get_any(&self, key: &str) -> Option<Arc<dyn Any + Send + Sync>>;
fn get_keyed_any(&self, key: &str, variant: &str) -> Option<Arc<dyn Any + Send + Sync>>;
// 默认实现的方法
fn get<T: ?Sized + Sync + Send + 'static>(&self) -> Arc<T;
fn try_get<T>(&self) -> Option<Arc<T>>;
fn get_keyed<T>(&self, variant: &str) -> Arc<T>;
fn try_get_keyed<T>(&self, variant: &str) -> Option<Arc<T>>;
}
工厂闭包中的依赖解析
工厂闭包接收 `&dyn IServiceResolver` 参数,可解析依赖:
ServiceCollection::new()
.singleton::<dyn IConfig>(|_| Arc::new(AppConfig))
.singleton::<dyn ILogger>(|r| {
let config: Arc<dyn IConfig> = r.get().unwrap(); // 解析依赖
Arc::new(FileLogger { level: config.level() })
})
.build()
.unwrap();
过程宏自动处理字段依赖:
// struct 上用 #[derive(Inject)] 仅生成构造函数(不注册),impl 上用 #[inject] 注册为 dyn ILogger
#[derive(Inject)]
struct FileLogger {
#[inject]
config: Arc<dyn IConfig>, // 自动解析
#[inject]
fallback: Option<Arc<dyn ILogger>>,
name: String, // 未标记 → 使用 Default::default()
}
#[inject]
impl ILogger for FileLogger { /* ... */ }
字段属性详解
显式注入策略:只有标记了 `#[inject(...)]` 的字段才从容器解析,未标记字段使用 `Default::default()`。 可选性由字段类型(`Option<...>`)决定,无需额外标记。
| 属性 | 字段类型 | 说明 | |------|---------|------| | *(无标记)* | 任意(需 `Default`) | 内部字段 — 使用 `Default::default()` | | `#[inject]` | `Arc<T>` | 必选共享 — 未注册返回 `Err(ServiceNotFound)` | | `#[inject]` | `Option<Arc<T>>` | 可选共享 — 未注册返回 `None` | | `#[inject(key = "k")]` | `Arc<T>` | 键控共享 — 找不到 key 返回 `Err(KeyedServiceNotFound)` | | `#[inject(key = "k")]` | `Option<Arc<T>>` | 可选键控共享 | | `#[inject(owned)]` | 裸 `T` | 必选 owned — 未注册或 Singleton 返回 `Err` | | `#[inject(owned)]` | `Option<T>` | 可选 owned — 未注册或 Singleton 返回 `None` | | `#[inject(owned, key = "k")]` | 裸 `T` / `Option<T>` | 键控 owned | | `#[inject(provider)]` | `Arc<dyn IServiceResolver>` | 注入 resolver 自身 |
> 严格类型校验:`#[inject]` 只接受 `Arc<T>` / `Option<Arc<T>>`, > `#[inject(owned)]` 只接受裸 `T` / `Option<T>`。标记与类型不匹配会触发编译错误。
#[inject(transient)]
struct Handler {
#[inject]
user_svc: Arc<dyn IUserService>, // 必选共享
#[inject]
logger: Option<Arc<dyn ILogger>>, // 可选共享
#[inject(key = "wechat")]
pay: Arc<dyn IPayGateway>, // 键控
#[inject(provider)]
resolver: Arc<dyn IServiceResolver>, // 注入解析器
name: String, // 未标记 → Default
}
最佳实践
| ✅ 推荐 | ❌ 避免 | |--------|--------| | `singleton::<dyn Trait>(...)` | `singleton(|_| Arc::new(Impl))` | | `provider.get::<dyn Trait>()` | `provider.get::<Impl>()` | | 字段声明 `Arc<dyn Trait>` | 字段声明 `Arc<Impl>` | | 面向接口:struct 用 `#[derive(Inject)]` + impl 用 `#[inject]` | struct 与 impl 同时用 `#[inject]`(双重注册) | | 仅注册为 trait(`#[inject] impl Trait for Type`) | 仅 `#[inject(singleton)]` 在 struct 上(不注册为 trait) |
---
下一节:[多实现与键控服务](multiple-implementations.md)