Search code examples
rusttraitsimplementationorphan-rule

Multiple `impl`s error while use-ing implementations within separated mods


Digging into the subject of the orphan rule, I ended up with a kind of implementation of a trait by a type both defined outside the implementing crate. But as a result, I now have another question about trait implementation. The following example works well:

orphan/ | c1/ | src/lib.rs | pub trait MyTrait<T> {
        |     |            |    fn my_task(&self);
        |     |            | }
        |     |
        |     | Cargo.toml -> DEFINITION BELOW
        |
        | c2/ | src/lib.rs | pub struct MyStruct;
        |     |
        |     | Cargo.toml -> DEFINITION BELOW
        |
        | c3/ | src/lib.rs | use c1::MyTrait; use c2::MyStruct;
        |     |            | pub enum MyT {}
        |     |            | impl MyTrait<MyT> for MyStruct {
        |     |            |    fn my_task(&self) { println!("This is c3 implementation"); }
        |     |            | }
        |     |
        |     | Cargo.toml -> DEFINITION BELOW
        |
        | c4/ | src/lib.rs | use c1::MyTrait; use c2::MyStruct;
        |     |            | pub enum MyT {}
        |     |            | impl MyTrait<MyT> for MyStruct {
        |     |            |    fn my_task(&self) { println!("This is c4 implementation"); }
        |     |            | }
        |     |
        |     | Cargo.toml -> DEFINITION BELOW
        |
        | c5/ | src/main.rs | mod _3 {
        |     |             |     use c1::*; use c2::*; use c3::*;
        |     |             |     pub fn f() { MyTrait::<MyT>::my_task(&MyStruct); }
        |     |             | }
        |     |             | mod _4 {
        |     |             |     use c1::*; use c2::*; use c4::*;
        |     |             |     pub fn f() { MyTrait::<MyT>::my_task(&MyStruct); }
        |     |             | }
        |     |             | fn main() { _3::f(); _4::f(); }
        |     |
        |     | Cargo.toml -> DEFINITION BELOW
        |
        | Cargo.toml | [workspace]
                     | members = [ "c1", "c2", "c3", "c4", "c5", ]

with result:

cargo run --release
   Compiling c5 v0.0.1 (XXX\orphan\c5)
    Finished release [optimized] target(s) in 0.27s
     Running `target\release\c5.exe`
This is c3 implementation
This is c4 implementation

But if I replace the main by:

main.rs | mod _3 {
        |    use c1::*; use c2::*; use c3::*;
        |    pub fn f() { MyTrait::my_task(&MyStruct); }
        | }
        | mod _4 {
        |     use c1::*; use c2::*; use c4::*;
        |     pub fn f() { MyTrait::<MyT>::my_task(&MyStruct); }
        | }
        | fn main() { _3::f(); _4::f(); }

the following error is obtained:

--> c5\src\main.rs:3:18
  |
3 |     pub fn f() { MyTrait::my_task(&MyStruct); }
  |                  ^^^^^^^^^^^^^^^^ cannot infer type for type parameter `T` declared on the trait `MyTrait`
  |
  = note: multiple `impl`s satisfying `c2::MyStruct: c1::MyTrait<_>` found in the following crates: `c3`, `c4`:
          - impl c1::MyTrait<c3::MyT> for c2::MyStruct;
          - impl c1::MyTrait<c4::MyT> for c2::MyStruct;

Why such error, while "use c3::...;" and "use c4::...;" are applied within separated mods?

By the way, the following case works perfectly (unused_imports allowed only to avoid warnings):

main.rs | mod _3 {
        |    use c1::*; use c2::*; #[allow(unused_imports)] use c3::*;
        |    pub fn f() { MyTrait::my_task(&MyStruct); }
        | }
        | fn main() { _3::f(); }

with result:

cargo run --release
   Compiling c5 v0.0.1 (XXX\orphan\c5)
    Finished release [optimized] target(s) in 0.28s
     Running `target\release\c5.exe`
This is c3 implementation

This behavior is thus a little bit strange : the compiler desagrees with the absence of turbofish only when both c3::MyT and c4::MyT are used, but that seems unlogic because they are used in separated mods.

ADD-ON: Detailed definition of the cargo files:

c1/Cargo.toml | [package]
              | name = "c1"
              | version = "0.0.1"
              | edition = "2021"
              | [dependencies]

c2/Cargo.toml | [package]
              | name = "c2"
              | version = "0.0.1"
              | edition = "2021"
              | [dependencies]

c3/Cargo.toml | [package]
              | name = "c3"
              | version = "0.0.1"
              | edition = "2021"
              | [dependencies]
              | c1 = { path = "../c1", version = "0.0.1" }
              | c2 = { path = "../c2", version = "0.0.1" }
        
c4/Cargo.toml | [package]
              | name = "c4"
              | version = "0.0.1"
              | edition = "2021"
              | [dependencies]
              | c1 = { path = "../c1", version = "0.0.1" }
              | c2 = { path = "../c2", version = "0.0.1" }
        
c5/Cargo.toml | [package]
              | name = "c5"
              | version = "0.0.1"
              | edition = "2021"
              | [dependencies]
              | c1 = { path = "../c1", version = "0.0.1" }
              | c2 = { path = "../c2", version = "0.0.1" }
              | c3 = { path = "../c3", version = "0.0.1" }
              | c4 = { path = "../c4", version = "0.0.1" }

Solution

  • Implementations seem ubiquitous through the entire crate as shown in example: [playground]

    trait Trait {
        fn my_fn();
    }
    
    enum Enum {}
    
    mod _nothing_mod {
        struct _Nothing;
        impl _Nothing {
            fn _do_nothing() {
                use crate::{ Enum, Trait, };
                impl Trait for Enum {
                    fn my_fn() { 
                        impl Enum {
                            pub fn new() -> Option<Self> {
                                println!("cannot create Enum instance");
                                None
                            }
                        }
                        println!("a task") 
                    }
                }
            }
        }
    }
    
    fn main() {
        Enum::new();
        Enum::my_fn();
    }
    

    resulting in:

    Standard Error
    
       Compiling playground v0.0.1 (/playground)
        Finished dev [unoptimized + debuginfo] target(s) in 0.60s
         Running `target/debug/playground`
    
    Standard Output
    
    cannot create Enum instance
    a task
    

    For this reason, it is not possible in the example of this question to use the two implementations of MyTrait for MyStruct in same crate c5 without using the turbofish.