How to keep doc of pub type alias of private type?

Background

In my project, MyStructA and MyStructB have very similar code structure, which may only differs in some fields' type, for example:

struct MyStructA {
    foo: u32
}

impl MyStructA {
    fn bar(&self) -> u32 { self.foo + 1 }
}

struct MyStructB {
    foo: u64
}

impl MyStructA {
    fn bar(&self) -> u64 { self.foo + 1 }
}

MyStructA and MyStructB have similar fields, derive macros, impl traits, methods ...

In order to have a nice code, I can extract the same code to a generic:

struct MyStruct<T> {
    foo: T
}

and just use newtype pattern:

type MyStructA = MyStruct<u32>;
type MyStructB = MyStruct<u64>;

Problem

Things become complicated when I want to add visibility.

I am writing a library, so for users, MyStruct<T> has no meaning, while MyStructA and MyStructB are what really matters. So:

  • MyStruct<T> may have private or pub(crate) visibility (and for not exposing private to public interface, I mark MyStruct<T> as pub inside a private module m)
  • MyStructA and MyStructB have pub visibility.

For now, the code becomes:

mod m {
    pub struct MyStruct<T> {
        foo: T
    }
    // and some impls for `MyStruct<T>`
}
pub type MyStructA = m::MyStruct<u32>;
pub type MyStructB = m::MyStruct<u64>;

When I use cargo doc to generate documentations, the doc for MyStructA and MyStructB only tell users that they are type alias to MyStruct, with no methods, fields, derive marco or impl marco in it. And since I intentionally make MyStruct<T> private, we can never know MyStructA and MyStructB have foo field or any other methods in the documentation.

There are two possible solutions here:

  • Is there any way where we can make doc of MyStruct<T> into type alias MyStructA and MyStructB?
  • Aside from newtype pattern, is there any way where we can write code once here, and generate two type MyStructA and MyStructB?

First of all, this pattern you're trying to do will not work in practice - see this playground:

mod library {
    struct MyStruct<T>(T);
    impl<T> MyStruct<T> {
        pub fn new(inner: T) -> Self {
            Self(inner)
        }
    }
    
    pub type MyStructA = MyStruct<u32>;
}

fn main() {
    let _ = library::MyStructA::new(0);
}

Errors:

warning: private type `library::MyStruct<u32>` in public interface (error E0446)
 --> src/main.rs:9:5
  |
9 |     pub type MyStructA = MyStruct<u32>;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(private_in_public)]` on by default
  = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
  = note: for more information, see issue #34537 <https://github.com/rust-lang/rust/issues/34537>

error: type `library::MyStruct<u32>` is private
  --> src/main.rs:13:13
   |
13 |     let _ = library::MyStructA::new(0);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^ private type

The reason is that type aliases are not types. They're simply names for existing types, so, if the existing type is private, type alias will be effectively private, too (and, according to the warning notes, this will be enforced later).


Second, in Rust community, "newtype pattern" is not the thing you've described. When speaking of "newtype", we usually mean the following:

struct MyStruct<T> {
    foo: T
}

struct MyStructA(MyStruct<u32>);
struct MyStructB(MyStruct<u64>);

i.e. newtype is simply a single-valued tuple struct. This, however, would probably not help, since MyStructA and MyStructB will have totally independent implementations, even if they both delegate to the inner one.


As for "what to do" - well, if you have a large bunch of exactly equivalent code, except some types, why not generate it with macro_rules?