2

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" }
FreD
  • 393
  • 2
  • 12
  • Are the Cargo.toml files really empty? They should have path dependencies, without it it errs for me. – Chayim Friedman Jul 01 '22 at 04:52
  • No, I decided not to give their detailed definition, because they are easy to define. Actually, They are built like in https://stackoverflow.com/questions/72733695/is-there-a-way-to-design-a-trait-that-allows-its-implementation-by-any-type-even – FreD Jul 01 '22 at 05:34
  • Then you can remove them from the body of the question, it is confusing. – Chayim Friedman Jul 01 '22 at 05:38
  • Updated! Your answer to my previous question has been removed, but there was informations really interesting for me. And it made me understand the reason of the orphan rule. It helped me a lot in finding this implementation trick. – FreD Jul 01 '22 at 05:53

2 Answers2

1

It is because your trait is generic, and there is no real way for Rust to figure out which type it should be without the turbofish (::<>). In this case, it seems obvious because there is only one impl, but that's not typical, because then there isn't really a point of it being generic.

To see this yourself, in c3, you can add a second impl block, and now it is pretty easy to see what it means by cannot infer type parameter T, it could just as easily be MyT or i32.

impl MyTrait<MyT> for MyStruct {
    fn my_task(&self) { println!("This is c3 implementation"); }
}
impl MyTrait<i32> for MyStruct {
    fn my_task(&self) { println!("This is c3 implementation for i32"); }
}

You can then specify either type in _3::f to disambiguate the function call:

MyTrait::<MyT>::my_task(&MyStruct);
MyTrait::<i32>::my_task(&MyStruct);

I don't have a good explanation on why your last example ("the following case works perfectly") doesn't experience the same issues though.

Jeremy Meadows
  • 2,314
  • 1
  • 6
  • 22
  • Strange. Last case work for me with result: Finished release [optimized] target(s) in 0.03s Running `target\release\c5.exe` This is c3 implementation – FreD Jun 30 '22 at 13:32
  • Did you try it without impl MyTrait ? – FreD Jun 30 '22 at 13:35
  • Oh yeah, I'd forgotten to delete that when I went back to rerun it, sorry. I don't guess I have a good answer on why that part works then, but my guess would be it's simple enough for Rust to make some stronger inferences? I hope the first part of my explanation was at least helpful. – Jeremy Meadows Jun 30 '22 at 13:46
  • Yes, thank you for your answer. I am used to test the limits of the language. Of course, it is not a problem to add the turbofish, but in this case, the behavior of the compiler is a little bit strange : it 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... – FreD Jun 30 '22 at 13:54
  • I would agree. I ran [cargo expand](https://github.com/dtolnay/cargo-expand) to fully-qualify every function call, and it doesn't look like there's anything special. hopefully somebody else can post a more complete answer – Jeremy Meadows Jun 30 '22 at 14:03
  • I have found my answer. Implementations of traits seem ubiquitous through the entire crate (cf. my answer). I am surprised by this fact, but that's an interesting point! – FreD Jul 13 '22 at 14:43
0

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.

FreD
  • 393
  • 2
  • 12