Improve compiler error message when failing to resolve types
If you try to resolve a type that hasn't been added, an error with the entire service provider type is emitted as well as a cryptic message saying the trait bound is not satisfied
error[E0277]: the trait bound `ServiceProvider<EmptyServiceProvider, HCons<TransientFactoryContainer<HCons<ControllerB, HNil>, SchemaB, [closure@teloc/tests/add_transient.rs:41:32: 44:10]>, HCons<TransientContainer<SchemaA>, HCons<TransientContainer<ControllerB>, HNil>>>>: Resolver<'_, _, ControllerA, _>` is not satisfied
--> teloc/tests/add_transient.rs:45:37
|
45 | let schema: SchemaA = container.resolve();
| ^^^^^^^ the trait `Resolver<'_, _, ControllerA, _>` is not implemented for `ServiceProvider<EmptyServiceProvider, HCons<TransientFactoryContainer<HCons<ControllerB, HNil>, SchemaB, [closure@teloc/tests/add_transient.rs:41:32: 44:10]>, HCons<TransientContainer<SchemaA>, HCons<TransientContainer<ControllerB>, HNil>>>>`
|
= note: required because of the requirements on the impl of `GetDependencies<'_, HCons<ControllerA, HCons<ControllerB, HNil>>, HCons<(_, _), HCons<(&TransientContainer<ControllerB>, (teloc::index::SelfIndex<There<There<Here>>>, HNil, HNil)), HNil>>>` for `ServiceProvider<EmptyServiceProvider, HCons<TransientFactoryContainer<HCons<ControllerB, HNil>, SchemaB, [closure@teloc/tests/add_transient.rs:41:32: 44:10]>, HCons<TransientContainer<SchemaA>, HCons<TransientContainer<ControllerB>, HNil>>>>`
= note: required because of the requirements on the impl of `Resolver<'_, &TransientContainer<SchemaA>, SchemaA, (teloc::index::SelfIndex<There<Here>>, HCons<ControllerA, HCons<ControllerB, HNil>>, HCons<(_, _), HCons<(&TransientContainer<ControllerB>, (teloc::index::SelfIndex<There<There<Here>>>, HNil, HNil)), HNil>>)>` for `ServiceProvider<EmptyServiceProvider, HCons<TransientFactoryContainer<HCons<ControllerB, HNil>, SchemaB, [closure@teloc/tests/add_transient.rs:41:32: 44:10]>, HCons<TransientContainer<SchemaA>, HCons<TransientContainer<ControllerB>, HNil>>>>`
I'm not sure how this would actually be implemented, but adding a message to provide a recommended fix could be useful. In this example, the recommendation could be something like
No container added for type ContainerA. Add the type ContainerA as Transient so that SchemaA can be resolved.
Ideally, we could provide customized error messages so that if you are trying to resolve a reference, you can recommend different solutions (such as providing a singleton or instance container).
This is not possible with current Rust - I've tried many different approaches and no one is working for teloc. There are two possible options:
- We need new feature in Rust which will allows us to define custom compiler error messages. Currently it is possible in
std, see Sync trait, but these attributes cannot be used even in nightly. - We can rethink current conception of the library - the more I wrote in the Rust, the more I tend to believe that such complex abstract things as DI should implemented using procedural macros. I'm not a big fan of procedural macros, so I'm not doing anything in Rust for the time being - I'm waiting for better times (which I suppose will never come). If you have enough confidence and time, you can try to implement a DI crate with procedural macros based on current
telocimplementation, like shaku and waiter-di.
If you interested in details - the problem is that I've used HList pattern which works pretty bad with Rust type system. Suppose you have HList type and following trait definition and implementations:
struct Here;
struct There<Idx>(Idx);
struct HNil;
struct HCons<A, R>(A, R);
trait Find<A, I> { fn check(&self) -> A; }
impl<A, Rest> Find<A, Here> for HCons<A, Rest> { fn check(&self) -> A { todo!() } }
impl<A, B, Rest, Index> Find<A, There<Index>> for HCons<B, Rest>
where
Rest: Find<A, Index>
{ fn check(&self) -> A { todo!() } }
fn test(arg: HCons<i32, HCons<i64, HNil>>) {
let a: u32 = arg.check();
}
When rustc tries to find implementation of the Find for the arg.check() call, type checker do following steps:
- It tries to apply
Find<A, Here>trait definition - it fails. - It tries to apply
Find<A, There<Index>> where Rest: Find<A, Index>trait definition - it fails. - It tries to find
Find<u32, There<There<_>>>trait definition - there are no possibility to construct such definition for the input type. - As no one definition is correct, error that "no implementation Find<u64, _> found for HCons<...>" will shown.
The problem of such approach is that if the top-level bound is not satisfied, type-checker will show you only top-level bound that is not satisfied, like note: required because of the requirements on the impl of Find<u32, There<_>> for HCons<i64, HNil>.