How to have a private function in a public trait

I have a trait that needs to be public so that it can be used as a constraint for a public function. The implementation of the trait is not meant to be public. This seems to be related to sealed traits ?

Here is an example of what I am trying to accomplish:

use std::convert::From;
//
// PrivateStruct
// This structure is private and not part of the API
struct PrivateStruct { }
//
// PublicTrait
// This trait is public because it is a constraint in check_le_max
pub trait PublicTrait {
    #[doc(hidden)]
    fn max(_ : Self, _ : PrivateStruct) -> Self;
}
impl PublicTrait for u8 {
    // This function is not part of the API
    #[doc(hidden)]
    fn max(_ : Self , _ : PrivateStruct) -> Self
    {   return Self::MAX }
}
//
// check_le_max
// This function is public because it is part of the API
pub fn check_le_max<T>(u : usize, _ : T) -> bool
where
    T     : From<u8> + PublicTrait ,
    usize : From<T> ,
{   let z_t  : T             = From::from(0u8);
    let z_ps : PrivateStruct = PrivateStruct{ };
    let max  = PublicTrait::max(z_t, z_ps );
    u <= From::from(max)
}
//
fn main() {
    println!( "( {} <= u8 max) = {}", 255, check_le_max(255, 0u8) );
    println!( "( {} <= u8 max) = {}", 256, check_le_max(256, 0u8) );
}

Here is the error message I am getting:

  --> src/main.rs:11:5
   |
11 |     fn max(_ : Self, _ : PrivateStruct) -> Self;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ associated function `PublicTrait::max` is reachable at visibility `pub`
   |
note: but type `PrivateStruct` is only usable at visibility `pub(crate)`
  --> src/main.rs:5:1
   |
5  | struct PrivateStruct { }
   | ^^^^^^^^^^^^^^^^^^^^
   = note: `#[warn(private_interfaces)]` on by default

You could seal your trait like this, for example:

use std::convert::From;

mod private {
    pub struct PrivateStruct {}

    pub trait PrivateTrait {
        fn max(_: Self, _: PrivateStruct) -> Self;
    }
}

use private::{PrivateStruct, PrivateTrait};

pub trait PublicTrait: PrivateTrait {}

impl PrivateTrait for u8 {
    // This function is not part of the API
    #[doc(hidden)]
    fn max(_: Self, _: PrivateStruct) -> Self {
        return Self::MAX;
    }
}

impl<T: PrivateTrait> PublicTrait for T {}

//
// check_le_max
// This function is public because it is part of the API
pub fn check_le_max<T>(u: usize, _: T) -> bool
where
    T: From<u8> + PublicTrait,
    usize: From<T>,
{
    let z_t: T = From::from(0u8);
    let z_ps: PrivateStruct = PrivateStruct {};
    let max = PrivateTrait::max(z_t, z_ps);
    u <= From::from(max)
}
//
fn main() {
    println!("( {} <= u8 max) = {}", 255, check_le_max(255, 0u8));
    println!("( {} <= u8 max) = {}", 256, check_le_max(256, 0u8));
}

Playground.

More in-depth trait sealing, including alternatives to the snippet above, can be found in this classic blog post:


Edit: you already have PrivateStruct in the method signature, so sealing via this would be more straight-forward and you can get rid of the private trait I introduced above:

use std::convert::From;

mod private {
    pub struct PrivateStruct {}
}

use private::PrivateStruct;

pub trait PublicTrait {
    fn max(_: Self, _: PrivateStruct) -> Self;
}

impl PublicTrait for u8 {
    fn max(_: Self, _: PrivateStruct) -> Self {
        return Self::MAX;
    }
}

//
// check_le_max
// This function is public because it is part of the API
pub fn check_le_max<T>(u: usize, _: T) -> bool
where
    T: From<u8> + PublicTrait,
    usize: From<T>,
{
    let z_t: T = From::from(0u8);
    let z_ps: PrivateStruct = PrivateStruct {};
    let max = PublicTrait::max(z_t, z_ps);
    u <= From::from(max)
}
//
fn main() {
    println!("( {} <= u8 max) = {}", 255, check_le_max(255, 0u8));
    println!("( {} <= u8 max) = {}", 256, check_le_max(256, 0u8));
}

Playground.

3 Likes

I do not understand why when you replaced

struct PrivateStruct {}

by the code below it fixed the problem ?

mod private {
    pub struct PrivateStruct {}
}

use private::PrivateStruct;

The former is a private type. The latter is an inaccessible (not exported) public type. The difference in visibility of PrivateStruct is important in this case. We can't leak a private type in a public interface, but we can use a public type in a public interface, even if we seal it behind a private module.

1 Like

It's exploiting a gotcha in the Rust's module system that you can have a type that is public, but the type can't be named.

AFAIK there isn't any profound design behind this, it's just a trick.

2 Likes

You might also consider making PrivateStruct a #[doc(hidden)] pub struct, but not constructible by downstream users.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.