Skip to content

← Modules, Visibility, Testing and Docs step 8 of 22

Hard Framework

pub use re-exports and the facade pattern

Every well-regarded Rust crate separates its file layout from its public API, and the tool for it is one keyword pair: pub use.

You organise the source however makes sense for the people editing it — engine/parts.rs, engine/wheels.rs, twelve private modules deep — and then, at the crate root, you publish a flat, curated surface:

mod engine;                       // private: nobody outside can say `engine::`

pub use engine::Engine;           // but this is public API
pub use engine::parts::Bolt;

Users write my_crate::Engine. You move Engine to a different file next Tuesday and nobody notices. That is the whole reason the pattern exists, and it leads directly to the definition that makes SemVer tractable:

A crate’s public API is the set of paths reachable from outside it — not the set of items marked pub.

The task

pub fn public_api(
    modules: Vec<(String, String)>,
    items: Vec<(String, String)>,
    reexports: Vec<(String, String)>,
) -> Vec<String>
  • modules and items are (path, visibility) pairs, visibility being "pub", "pub(crate)" or "private".
  • reexports are (containing_module, target_path) pairs, each meaning pub use <target>; written in <containing_module>.

Return every path by which something is reachable from outside the crate, sorted — plus the marker "E0364:<target>" for any re-export whose target is narrower than pub.

Three things the tests are checking

An item in a private module is invisible — until it is re-exported. The original path disappears (nobody outside can name crate::inner), and the re-export path appears. pub use short-circuits the privacy chain: it does not make inner public, it makes the item reachable by a new route.

An item can be reachable twice. If the containing module is also pub, you get both paths, and both are API you now have to keep working. rustdoc will render the type twice unless you add #[doc(no_inline)]. Facade crates generally keep the inner modules private for exactly this reason.

A re-export cannot widen anything. pub use inner::T; where T is only pub(crate) is E0364 — T is only public within the crate, and cannot be re-exported outside. And you get a second, confusing diagnostic for free: an unused import warning, because the failed re-export binds nothing. Two errors, one mistake. When you see that pair, look at the target’s visibility, not at the use line.

A pub use written inside a private module is not an error at all — it just creates no externally reachable path, since the module it lives in is not reachable either.

pub_use is a restriction lint, and some teams turn it on

clippy::pub_use forbids the pattern outright. The argument is that re-exports make it harder to see where a type actually comes from, and that a reader chasing my_crate::Engine has to grep. The counter-argument is that a crate without a facade has its file layout welded into its SemVer surface forever.

Both positions are real. pub_use is restriction, which means “we implemented the check, you decide the policy” — not “this is bad Rust”.

Contract

  • The crate root is "crate" and is always externally reachable.
  • Every module on a path appears in modules. A re-export whose target is not in items is ignored.
  • Re-exports are single-hop: no re-export targets another re-export.
  • Sort the result lexicographically; markers and paths share one list.