Public API depending on private one

This might look like a strange problem, but it have really arisen in one on my projects.

The main project is the library, exposing two traits - "basic" one and "wrapper" one. The intended usage is following:

  • some other ("data") crate implements the base trait;
  • second (wrapper) trait is implemented automatically by the library (and can't be overridden);
  • users of the "data" crate will use this wrapper API.

The problem is that I can't stop them from using the basic API directly, which might break the wrapper logic.

That's how it looks in code (simplified):

// imagine that this is the library crate...
mod lib {
    pub trait Base: Sized {
        fn private() -> Self;
    }
    pub trait Wrapper: Base {
        fn public() -> Self {
            Self::private()
        }
    }
    impl<T: Base> Wrapper for T {}
}

// ...this is the other crate using it...
mod data {
    use crate::lib::Base;
    pub struct Data;
    impl Base for Data {
        fn private() -> Self {
            Data
        }
    }
}

// ...and this is the third crate, using the "data":
mod other {
    use crate::data::Data;
    fn usage() {
        // I want to prevent this...
        use crate::lib::Base;
        let _ = Data::private();
        // ...forcing instead this:
        use crate::lib::Wrapper;
        let _ = Data::public();
    }
}

(Playground)

Is the thing I'm trying to do ever possible, or should I redesign the library somehow?

If you can use more "wrapper" structs you could make it work with deref/autoref coercion playground. Have the empty struct instantiated have the private method that will return the Data ? something like that maybe?

mod data {
    use crate::lib::Base;
    
    #[derive(Clone)]
    pub struct Data {}

    impl Base for &Data {
        fn private(&self) -> Self {
            self.clone()
        }
    }
}

I know I have seen an example of this were the private method panic!ed if I can remember where I'll add the link.

This is not really a solution, since it allows for (&data).private() and the like.

However, I've found the possible way - it is complicating the internal API a bit (i.e. may be a bit confusing for "data" crate author), but works OK for external users:

// imagine that this is the library crate...
mod lib {
    // private struct, impossible to create from outside
    struct InnerMarker;
    // public struct, however impossible to create from outside due to inner field
    pub struct Marker(InnerMarker);

    pub trait Base: Sized {
        // Marker argument should not be used, but it prevent users
        // from calling this method from outside
        fn private(_: Marker) -> Self;
    }
    pub trait Wrapper: Base {
        fn public() -> Self {
            // Since this implementation is the only one, this is valid
            Self::private(Marker(InnerMarker))
        }
    }
    impl<T: Base> Wrapper for T {}
}

// ...this is the other crate using it...
mod data {
    use crate::lib::{Base, Marker};
    pub struct Data;
    impl Base for Data {
        fn private(_: Marker) -> Self {
            Data
        }
    }
}

// ...and this is the third crate, using the "data":
mod other {
    use crate::data::Data;
    fn usage() {
        // This is impossible now, since user can't create `Marker` outside the `lib` crate
        // use crate::lib::Base;
        // let _ = Data::private(marker);
        // ...but this is still OK:
        use crate::lib::Wrapper;
        let _ = Data::public();
    }
}

Playground

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.