3

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?

Florian Brucker
  • 9,621
  • 3
  • 48
  • 81
  • Make the module public to super, then from super expose just the concrete type as an alias. – Netwave Nov 24 '21 at 21:20
  • @Netwave That sounds interesting, but I can't really figure out how I would do that. Could you please post a short example in an answer? – Florian Brucker Nov 24 '21 at 21:34
  • I added an answer with the idea, it didnt actually worked with the `pub(super)` but the main idea behind did. – Netwave Nov 24 '21 at 21:44

2 Answers2

4

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

Netwave
  • 40,134
  • 6
  • 50
  • 93
  • Thanks, this is great. My only issue with it is that the documentation generated by `cargo doc` for `FooBar` is lacking. However, that's probably a different issue, so I posted [a separate question](https://stackoverflow.com/q/70226914/857390). – Florian Brucker Dec 04 '21 at 15:25
1

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.

Stargateur
  • 24,473
  • 8
  • 65
  • 91
  • Your example differs from mine in a significant part: you hide `Foo` from the public API by making it a private field in `FooBar`. However, the users of my API need access to `Foo` (my public `foo` function returns an instance of `Foo`). In my real use case the types are much more complex, so hiding the generic `Foo` and only exposing `Foo` under a new name improves the usability greatly. It also hides the implementation details, so I can implement the public type differently in the future without breaking the API. Is there a way to achieve that with your approach? – Florian Brucker Dec 04 '21 at 20:37
  • 1
    @FlorianBrucker: "my public `foo` function returns an instance of `Foo`"—but it could (and probably *should*) return an instance of `FooBar`, thereby properly hiding the `Foo` implementation detail. – eggyal Dec 04 '21 at 20:41