Use of pub for non public APIs

I've noticed a pattern in popular crates to:

  • Declare a module as public.
  • Prefix it with __ to visually mark it as actually being private.
  • Prepend #[doc(hidden)]
  • Add a comment to the effect of “This is not a public API”.

For example, from the quote crate:

And from the log crate:

Why is this pattern of using pub to expose a module/function to the root used if the author does not want to allow access? It broke my mental model of Rust so far, which is that the pub keyword used at the root level exposes a function or module to the outside world to denote it as safe for general use.

Is there a reason in this case that these public functions/modules can't be kept private as described in the reference?

A crate needs a global available "helper module" to itself, but it doesn't want to expose the helper module as a public API. To accomplish this, the root of the crate's hierarchy would have a private module which then internally has a "public API". Because the entire crate is a descendant of the root, then the entire local crate can access this private module through the second case.

https://doc.rust-lang.org/reference/visibility-and-privacy.html

1 Like

I've often seen this done for items which are logically internal, but downstream crates may need access to.

For example, say crate A exports a foo!() macro which calls some internal __private_func() to do something. Crate B (which depends on crate A) tries to use foo!(), but because the macro gets expanded in the context of crate B it needs to be able to call crate_a::__private_func(). If you don't mark the __private_func() as pub then this will fail with privacy errors, and your foo!() macro will be unusable.

This convention of leading underscores and #[doc(hidden)] is kinda like the [InternalsVisibleTo("SomeOtherLibrary")] attribute in C#, except more hacky.

3 Likes

Apart from macros, it's also a crutch for testing. I expose methods this way so that tests in tests/ directory can access them (because #[cfg(test)] does not work in libraries in such case).

5 Likes

I've done this with the time crate. In order to use the result of a proc macro (nb: different crate) in a const context, it was necessary to omit data validity checks in favor of doing them in the proc macro crate. Because exposing it publicly could result in internally inconsistent state, I made the decision to place it in a mod internal.

Note that this specific situation could be avoided by allowing a crate to expose multiple libs, but that definitely isn't sufficient for every case.

1 Like

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