Exposing only a concrete variant of a generic private type in a Rust library

Viewed 141

I have a Rust library crate with code that is structured as follows:

pub struct Foo<X> {
    x: X,
}

pub fn foo() -> Foo<u32> {
    // ...
}

// + private functions

In particular, while the lib uses different variants of Foo internally (e.g. Foo<u8>, Foo<u32>), Foo only occurs in the lib's public API as Foo<u32>.

Exposing the generic Foo as I currently do makes the lib's public API and its documentation unnecessarily complex: the user will never get a Foo from the library that is not a Foo<u32>. Therefore, I'd like to somehow only expose and publicly document Foo<u32> (ideally under a different, non-generic name, e.g. Bar) and make Foo private.

I've tried using a type alias (type Bar = Foo<u32>), but it seems that those are automatically expanded by cargo doc (and they also don't seem to have separate visibility).

I could probably copy the definition of Foo<X> and call it Bar instead, and then implement something like From<Foo<u32>> for Bar. However, my actual definition of Foo<X> is fairly complex, so I'd like to avoid that.

Is there another way to achieve my goal?

2 Answers

You can expose the type from the parent module as follows:

mod prelude {
    mod foo_mod {
        pub struct Foo<X> {
            x: X,
        }

        impl Foo<u32> {
            pub fn foo() -> u32 {
                32
            }
        }

        impl Foo<u8> {
            pub fn foo() -> u8 {
                8
            }
        }
    }

    pub type FooBar = foo_mod::Foo<u32>;
}


fn main() {
    use prelude::FooBar; // we can use this
    use prelude::foo_mod::Foo; // we cannot use this
}

Playground

I disadvice Netwave trick, it's cheating the rule of public and private of Rust, In my opinion such code shouldn't compile. This expose Foo to user and so in reality Foo is totally public and any change to Foo is breaking change.

From your problem description:

In particular, while the lib uses different variants of Foo internally (e.g. Foo, Foo), Foo only occurs in the lib's public API as Foo.

The solution is make a true wrapper:

pub struct FooBar {
  foo: Foo<u32>,
}

impl FooBar {
  pub fn some_pub_fct();
}

This is what you should do cause as you said, user don't need the generic, so what you want is hide implementation detail of the user. This is way better than netwave trick that leak private item. This way is clear that change FooBar is a breaking change cause it's suppose to be a public item. And you can change Foo as much as you like. User win, you win.

Related