Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to have a private part of a trait?

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.
    }
}
like image 464
Boiethios Avatar asked Nov 08 '18 08:11

Boiethios


2 Answers

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);
}
like image 130
lhk Avatar answered Nov 13 '22 01:11

lhk


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
like image 2
alextes Avatar answered Nov 13 '22 00:11

alextes