waiter icon indicating copy to clipboard operation
waiter copied to clipboard

Support traits defined in crate

Open schneidersteve opened this issue 4 years ago • 5 comments

I extended the 3_inject_options_list.rs example to implement Interface4 from crate test_def.

use test_def::Interface4;

#[provides]
impl Interface4 for Comp {
    fn int4(&self) {
        println!("Interface 4");
    }
}

I get the following compiling error:

cargo run --color=always --package waiter_di --example 3_inject_options_list
   Compiling waiter_di v1.6.4 (/home/steve/workspaces/playground/rust/waiter)
error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
   --> examples/3_inject_options_list.rs:116:1
    |
116 | #[provides]
    | ^^^^^^^^^^^
    | |
    | impl doesn't use only types from inside the current crate
    | `waiter_di::Container` is not defined in the current crate
    | `dyn Interface4` is not defined in the current crate
    |
    = note: define and implement a trait or new type instead
    = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to previous error

For more information about this error, try `rustc --explain E0117`.
error: could not compile `waiter_di`

schneidersteve avatar Dec 23 '20 12:12 schneidersteve

Hi, I mentioned similar thing in section "Factory functions". You can find more about this here. In brief, in Rust you can't implement an interface/trait from crate A for a type/struct from crate B in crate C.

The main idea, that lies behind this library - you don't need to register all components in container. To achieve this, code generator behind #[component] creates implementation of Provider<INTERFACE> trait for each interface marked by #[provides]. And this includes restrictions of Rust traits. You have next options to overcome this:

  1. put
#[provides]
impl Interface4 for Comp {

in test_def crate 2. put Interface4 in the example file. 3. Create new trait that Inherits external trait

In the real world it means that all implementations of trait must be defined in the same crate if you are using waiter library.

dmitryb-dev avatar Dec 23 '20 13:12 dmitryb-dev

I tried 3. without success. Could you please provide a example. Even Inheritance with Traits didn't worked for me.

This restrictions makes the implementation of a Hexagonal Architecture with crates and DI not easy.

schneidersteve avatar Dec 23 '20 13:12 schneidersteve

It's only question of preferences. You need to keep interfaces and implementations in the same crate. If it will be the same app, you can use Rust modules instead.

dmitryb-dev avatar Dec 23 '20 17:12 dmitryb-dev

I’m trying to create a small framework for handling modules and just came across this di crate which seems awesome, but this issue kinda got me thinking.

If I have a trait OnModuleInit which is like a type blueprint for a lifecycle method in a framework crate, now in the actual crate I would like to implement this, this means I have to choose one of the above methods? Is there really no way if making this work. Seems like a huge nono for me

somehowchris avatar Apr 09 '22 17:04 somehowchris

You can read about this here: https://www.reddit.com/r/rust/comments/7agy4b/could_we_ever_implement_traits_for_structs/ https://users.rust-lang.org/t/how-do-i-implement-an-external-trait-for-an-external-struct/38623/39 https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types

I also frustrated by this. But actually the main target of DI container is to simplify the project setup and remove boilerplate code. Otherwise I don't understand why somebody would use DI library when code with DI is more verbose then without using DI, it's like "using DI just to use DI". So conciseness was "must have" when I was choosing some trade off here.

Anyway. What we can consider:

  1. To make some investigation. Maybe rust has got some new feature that will provide some workaround here. Idk right now.
  2. We can implement it in the next way. If we can't implement Provider trait for external crate struct, we can define another Provider trait in this external crate. So we will be able to create new trait like ExternalCrateProvider by using trait inheritance https://users.rust-lang.org/t/extending-traits/1802. And after that we can pass it to generator like #[provides(ExternalCrateProvider)] and use it like ExternalCrateProvider::get(&container). It's ugly and gives another limitations, this is why I didn't implemented this earlier. But probably I'll try do this is as a workaround. But as far as I remember I already tried and this didn't work or was too verbose.

dmitryb-dev avatar Apr 11 '22 12:04 dmitryb-dev