Search code examples
rusttraitsimplementationorphan-rule

Is there a way to design a trait that allows its implementation by any type even if the implementer owns neither the type nor the trait?


In general case, it is not possible to implement in crate C1 a trait defined in C2 for a Type defined in crate C3, being assumed that C1, C2, C3 are different

But is there a trick to designing a trait on purpose, so that such implementations are allowed?


Solution

  • Since Rust 1.41, it seems that Rust’s orphan rules have been a little bit more relaxed, as explained in Into doc.

    The key is to make the trait dependent on a type parameter which is defined in the implementing crate. For example, the following, crate workspace "orphan", will actually work:

    orphan/ | c1/ | src/lib.rs | pub trait MyTrait<T> {
            |     |            |    fn my_task(&self);
            |     |            | }
            |     |
            |     | Cargo.toml | [package]
            |                  | name = "c1"
            |                  | version = "0.0.1"
            |                  | edition = "2021"
            |                  | [dependencies]
            |
            | c2/ | src/lib.rs | pub struct MyStruct;
            |     |
            |     | Cargo.toml | [package]
            |                  | name = "c2"
            |                  | version = "0.0.1"
            |                  | edition = "2021"
            |                  | [dependencies]
            |
            | c3/ | src/lib.rs | use c1::MyTrait; use c2::MyStruct;
            |     |            | pub enum MyT {}
            |     |            | impl MyTrait<MyT> for MyStruct {
            |     |            |    fn my_task(&self) { println!("hello!"); }
            |     |            | }
            |     |
            |     | 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/ | src/main.rs | fn main() {
            |     |             |     use c1::MyTrait; use c2::MyStruct;
            |     |             |     #[allow(unused_imports)] use c3;
            |     |             |     MyStruct.my_task();
            |     |             | }
            |     |
            |     | 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" }
            |                  | c3 = { path = "../c3", version = "0.0.1" }
            |
            | Cargo.toml | [workspace]
                         | members = [ "c1", "c2", "c3", "c4", ]
    

    with result:

    cargo run --release
       Compiling c4 v0.0.1 (XXX\orphan\c4)
        Finished release [optimized] target(s) in 0.26s
         Running `target\release\c4.exe`
    hello!
    

    In conclusion, there is a trick to design a trait in crate c1, so there is a way in some sense to implement this trait in crate c3 for a type defined in crate c2, assuming c1 , c2, c3 are different!