31

In a crate I write, I have a bunch of internal structs public to the user and that share some code. Some of the shared code is public, some is an internal implementation. To share efficiently the code, I am using macros, but now that the project has more features, this begins to be messy, and I am not satisfied by the semantic of this.

I would like to use a trait, but without exposing the implementation. For example:

pub trait MyTrait {
    type Next;

    // This function is for the user.
    fn forward(&self) -> Self::Next {
        self.do_the_job()
    }

    // This function is for the user.
    fn stop(&self) {
        self.do_the_job();
    }

    // This function is an implementation detail.
    fn do_the_job(&self) -> Self::Next;
}

I want the user to see and use forward and stop, but not do_the_job, while my data would only implement do_the_job.

Is it possible to design my code to do something like that? I have tried to imagine some solutions, but nothing has come to my mind.

Playground


In an object oriented language with inheritance, I would do (pseudo code):

public interface MyTrait {
    type Next;

    fn forward(&self) -> Self::Next;

    fn stop(&self);
}

public abstract class MyCommonCode extends MyTrait {
    fn forward(&self) -> Self::Next {
        self.do_the_job()
    }

    fn stop(&self) {
        self.do_the_job();
    }

    protected abstract fn do_the_job(&self) -> Self::Next;
}

public MyType extends MyCommonCode {
    type Next = i32;

    protected override fn do_the_job(&self) -> Self::Next {
        // etc.
    }
}
Boiethios
  • 38,438
  • 19
  • 134
  • 183
  • You can put the private function in a private trait, and make the public trait depend on it. – Sven Marnach Nov 08 '18 at 08:59
  • @SvenMarnach I don't think that it will work in this context. I tried a few different solutions, but cannot satisfy the compiler. Do you have a POC? – hellow Nov 08 '18 at 09:10
  • @SvenMarnach This is forbidden: `private trait in public interface`. – Boiethios Nov 08 '18 at 09:24
  • You need to put the trait in a private module, but make the trait itself public. The associated type is a further complication, though. – Sven Marnach Nov 08 '18 at 09:25
  • 1
    @Boiethios Here's an [example on the playground without the associated type](https://play.rust-lang.org/?version=beta&mode=debug&edition=2018&gist=6a4c94b9ec3eca0872453d7014231a35). – Sven Marnach Nov 08 '18 at 09:26
  • This actually works with the associated type as well. – Sven Marnach Nov 08 '18 at 09:30
  • @SvenMarnach Well, this is crazy: you cannot leak a private type, but you can leak a public type in a private module... I will accept your answer when you post it. – Boiethios Nov 08 '18 at 09:36
  • 1
    Yep, `byteorder` does that as well https://docs.rs/byteorder/1.2.7/src/byteorder/lib.rs.html#166 to protect others from implementing their trait – hellow Nov 08 '18 at 09:40
  • @SvenMarnach: This public trait in private message not triggering the warning seems more like an oversight than a desired behavior to me... – Matthieu M. Nov 08 '18 at 09:52
  • @Boiethios: It is not clear from your example whether `MyType` is supposed to be an internal struct or a struct written by a user of the library; could you specify? – Matthieu M. Nov 08 '18 at 09:55
  • @MatthieuM. `MyType` is an internal struct. I want to have a trait only to share the code. – Boiethios Nov 08 '18 at 10:07
  • Thanks to @SvenMarnach, I've a working code: https://play.rust-lang.org/?version=beta&mode=debug&edition=2018&gist=f6c8a751095c008fd86ef12cff572547 – Boiethios Nov 08 '18 at 10:08
  • @MatthieuM. I've seen this pattern in the wild for a few times, but thinking about it, you are right. Something feels off here. – Sven Marnach Nov 08 '18 at 10:32
  • @Boiethios I'm not posting an answer – this doesn't feel quite right, but I don't know a better way. One problem I see is that you now have a public method returning a private associated type. So you can't really name the return type of `forward()` in application code using `MyTrait`, which seems odd. There are workarounds, like introducing yet another public trait containing the associated type, and making the private trait depend on it, but I'm still not convinced this is the way to go. – Sven Marnach Nov 08 '18 at 10:36
  • Related: https://stackoverflow.com/questions/41326566/is-there-a-way-to-have-private-functions-in-public-traits – Sven Marnach Nov 08 '18 at 10:42
  • @MatthieuM. [The "sealed" pattern is even mentioned in the API guidelines](https://rust-lang-nursery.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed), so it doesn't seem to be an oversight after all. – Sven Marnach Nov 08 '18 at 10:48
  • @SvenMarnach I don't think that the associated type is an issue. It is only an alias: when you write `type Next = MyPublicType;`, everything is ok. – Boiethios Nov 08 '18 at 11:00
  • @Boiethios The problem occurs if you accept a generic type parameter `` and want to name the associated type. You'd need to write `::Next`, but can't, since `DoTheJob` is inaccessible. – Sven Marnach Nov 08 '18 at 11:10
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/183293/discussion-between-boiethios-and-sven-marnach). – Boiethios Nov 08 '18 at 11:12

2 Answers2

19

Traits are similar to interfaces:

Traits are Rust’s sole notion of interface.

An interface is meant to document available methods, to have an interface with private methods makes no sense. Correspondingly, in Rust you can't have different levels of visibility in one trait. If you can see the trait, you can always see all of it. However, Rust traits are subtly different from interfaces: they combine declarations and implementations. I see how it would be intuitive to have a trait with some private functions.

For some time it was possible to split a trait into a public and private part. You would have two traits, one containing your public interface, the other with your private functionality, but this is being removed in newer versions of Rust.

The current workaround is still splitting the trait, but the private part must now be represented by a public trait within a private module. To explain this, here is some sample code:

// this module contains a public trait Inc, to increment a value
// and it implements it by using a private trait Add
mod my_math {
    pub struct Val {
        pub val: i32,
    }

    // this is necessary to encapsulate the private trait
    // the module is private, so the trait is not exported
    mod private_parts {
        pub trait Add {
            fn add(&mut self, i32);
        }
    }

    // in the following code, we have to use adequate namespacing
    impl private_parts::Add for Val {
        fn add(&mut self, other: i32) {
            self.val += other;
        }
    }

    pub trait Inc: private_parts::Add {
        fn inc(&mut self);
    }

    impl Inc for Val {
        fn inc(&mut self) {
            use my_math::private_parts::Add;
            self.add(1)
        }
    }
}

fn main() {
    use my_math::Inc;
    let mut b = my_math::Val { val: 3 };
    println!("value: {}", b.val);
    b.inc();
    println!("value: {}", b.val);
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
lhk
  • 27,458
  • 30
  • 122
  • 201
  • 4
    I've read up a bit on this, and it looks like making a public trait dependent on a private trait _won't_ become an error in the future. See allso [RFC 2145](https://github.com/rust-lang/rfcs/blob/master/text/2145-type-privacy.md) for the new proposed rules. – Sven Marnach Nov 08 '18 at 13:08
  • Could you even make `inc` a default method? – Solomon Ucko Nov 17 '22 at 20:35
  • That's an interesting way to do it. But it requires that all implementations of the `Inc` trait implement `Add` and its methods too, and `Add` isn't really private, on the contrary: it's declared as public and it forces to implement its methods. To come back to the OP's question, I would rather implement the `MyTrait` trait in a module for each target type, in which I add simple functions that I call from the trait methods. That way, they are really private, and they are not exposed or mandatory because of a supertrait. – RedGlyph Jan 11 '23 at 10:07
  • Rust's traits aren't exactly interfaces--notably, they let you define default implementations directly on the traits. For the same reason that a non-default implementation might want to make use of a private function, so might the default implementation. Relatedly, you can define functions on a trait `impl for T { ... }` which also might want to make use of private methods. – weberc2 Apr 23 '23 at 16:28
3

How about putting the private method into the type's methods rather than the trait's methods? You can make the type's methods non-pub if you want to:

The following playground:

mod lib {
    pub trait MyTrait {
        type Next;
    
        // This function is for the user.
        fn forward(&self) -> Self::Next;
        
        // This function is for the user.
        fn stop(&self);
    }
    
    pub struct MyType;
    
    impl MyTrait for MyType {
        type Next = i32;
        
        fn forward(&self) -> Self::Next {
            self.do_the_job()
        }
        
        fn stop(&self) {
            self.do_the_job();
        }
    }
    
    impl MyType {
        // This function is an implementation detail.
        fn do_the_job(&self) -> i32 {
            0
        }
    }
}

fn main() {
    use crate::lib::*;

    let foo = MyType;
    foo.forward(); // Works
    foo.stop(); // Works
    foo.do_the_job(); // Should not be visible or possible and fails at compile-time.
}

This does what you want because calling the method you wanted to be private from outside the type's own methods now fails to build with the error:

   Compiling playground v0.0.1 (/playground)
error[E0624]: associated function `do_the_job` is private
  --> src/lib.rs:40:9
   |
28 |         fn do_the_job(&self) -> i32 {
   |         --------------------------- private associated function defined here
...
40 |     foo.do_the_job(); // Should not be visible or possible and fails at compile-time.
   |         ^^^^^^^^^^ private associated function

For more information about this error, try `rustc --explain E0624`.
error: could not compile `playground` due to previous error
Chai T. Rex
  • 2,972
  • 1
  • 15
  • 33
alextes
  • 1,817
  • 2
  • 15
  • 22
  • 1
    @ChaiT.Rex perhaps I'm missing something or could make my code more clear but isn't this exactly what the OP is asking for? You _can_ call `forward` and `stop`. You cannot call `do_the_job` from outside the implementation. The above code shows trying to call `do_the_job` results in a compile error _which is what the OP wants_. Am I missing something? – alextes Oct 04 '22 at 12:49
  • Edited the answer to try and make the intentional build error more obvious. – alextes Oct 04 '22 at 12:56
  • That doesn't strictly answer my question, but it may be useful for someone looking for this problem. – Boiethios Nov 03 '22 at 17:07
  • 1
    The OP does not want a compile error, the OP wants to avoid rewriting certain methods every time the trait is implemented. The goal is polymorphism: any thing which can provide a concrete implementation of `do_the_job` then gets `forward` and `stop` to come along for free. However, `do_the_job` is not meant to be public, while `forward` and `stop` are. – kbolino Nov 03 '22 at 17:52