When your crate's API doesn't match its filesystem
You've built a Rust library. Internally it has half a dozen modules: parser, tokenizer, error, config, all neatly organized by purpose. But when somebody else wants to use your crate, you don't want them writing my_crate::parser::ast::Node for every little thing. You want them writing my_crate::Node. The internal layout is your problem; the public surface should be clean and small.
That's where pub use comes in. It lets the inside of your crate look one way and the outside look another. The mechanism is called re-exporting, and it's one of those features that seems trivial until you realize how much of every well-designed crate quietly depends on it.
The basic shape
Imagine src/lib.rs of a tiny library:
// An internal module. We mark it `pub` so its items can flow out,
// but we'll re-export the parts we want callers to use directly.
pub mod parser {
pub struct Ast {
pub nodes: Vec<String>,
}
pub fn parse(input: &str) -> Ast {
Ast { nodes: input.split_whitespace().map(String::from).collect() }
}
}
// Re-export the items at the crate root. Now `my_crate::Ast` and
// `my_crate::parse` work, even though they're really inside `parser`.
pub use parser::{Ast, parse};
A user of your crate will write:
use my_crate::{Ast, parse};
fn main() {
let tree = parse("hello world");
println!("{} nodes", tree.nodes.len());
}
They never have to know that parser exists. If you reorganize and rename parser to frontend next month, you only have to update the pub use line and your users' code keeps working unchanged.
Why "pub use" instead of "use"
Plain use is just a shortcut. It brings a name into scope for the current module. pub use does the same thing, but also makes the imported name part of that module's public API.
mod inner {
pub fn ping() {}
}
// Plain `use`: `ping` is visible inside this file but not exported.
use inner::ping;
// pub use: `ping` is now reachable from outside the crate as `crate::ping`.
pub use inner::ping;
If you forget the pub, your users get errors like:
error[E0603]: function `ping` is private
The fix is just to add pub in front.
What it really gives you
Three things you should know about.
Decoupling layout from API. Your file structure can mirror your internal architecture (one file per concept, neat sub-folders). Your public API is whatever you pub use at the crate root. They never have to match.
Stable interfaces over time. When you refactor, the path on the inside changes. The path on the outside doesn't, as long as you keep the re-export pointing at the new location. Your users see no breakage.
Curating the surface. Big crates often have many internal types that exist for implementation reasons but aren't meant to be used directly. pub use lets you elevate the handful of things that should be the front door, leaving the rest reachable but not advertised.
The serde crate is a famous example: nearly its entire user-facing API is re-exports from internal modules, presented through a flat top-level namespace.
Re-exporting from a sub-module
pub use isn't limited to lib.rs. You can use it inside any module to flatten the view of that module's children.
// src/lib.rs
pub mod io {
pub mod reader;
pub mod writer;
// Re-export at the io module level. Now users of `my_crate::io`
// get `Reader` and `Writer` directly without typing the inner module names.
pub use reader::Reader;
pub use writer::Writer;
}
A user does:
use my_crate::io::{Reader, Writer};
without knowing or caring that there's a reader submodule inside io. The standard library uses this pattern heavily. std::io::Result is actually std::io::Result, but defined inside a submodule and re-exported.
Renaming on the way out
Sometimes the internal name and the public name should differ. Maybe internal::FooImpl is fine inside but you want to expose it as just Foo. Use as:
pub use internal::FooImpl as Foo;
This is also handy for resolving conflicts when two re-exports would collide:
pub use crate::v1::Client as ClientV1;
pub use crate::v2::Client as ClientV2;
A more realistic example: a small library facade
Here's a layout that's typical for a published crate:
// src/lib.rs
// Internal modules. Each holds its own types and functions.
mod config;
mod connection;
mod error;
mod query;
// The public faΓ§ade. Everything users should see lives at the root.
pub use config::Config;
pub use connection::Connection;
pub use error::{Error, Result};
pub use query::{Query, QueryBuilder};
// Hidden but documented escape hatches: exposed for power users
// who want the full path. The leading underscore module stays public.
pub mod internal {
pub use crate::connection::raw_socket;
}
Notice three things. The internal modules are declared mod, not pub mod: their items are reached only through the re-exports. The Result type is re-exported alongside Error, which is a common pattern (so callers can write my_crate::Result<T> instead of std::result::Result<T, my_crate::Error>). And the internal module is a deliberate "I see you, power users" escape hatch for things that aren't part of the curated faΓ§ade but shouldn't be impossible to reach.
That last pattern is worth copying. Re-export the things 95% of users want at the crate root, but leave a mod internal or mod raw for the people who need to break the abstraction.
Common pitfalls
You forgot pub in front of use:
error[E0603]: function `helper` is private
Fix: change use to pub use.
You re-exported a type that depends on private types. The compiler will complain that you're "leaking a private type in a public interface" (E0446):
error[E0446]: private type `Internal` in public interface
Fix: either make the private type public, or stop re-exporting the type that depends on it.
Your module isn't even reachable. If lib.rs has mod parser; (without pub), the items inside aren't accessible outside the crate. pub use parser::Foo is fine because pub use looks inside even non-pub modules. But parser::Foo directly from outside won't work. This is exactly the trick: mod parser; keeps the module private, pub use parser::Foo; exposes only what you choose.
You re-exported a glob (pub use submodule::*) and accidentally exposed things you didn't mean to. Glob re-exports are convenient but dangerous: if you add a new public item to the inner module later, it's automatically part of your public API. Prefer explicit lists for stability.
You re-exported across crates. Yes, you can pub use other_crate::Thing; and your downstream users will see your_crate::Thing. Useful for prelude-style crates, but be aware: bumping other_crate's major version is now a breaking change for your users too.
When to use what
Reach for pub use whenever the thing you want users to type doesn't match where the item naturally lives in your code. That's most of the time, in any project bigger than a single-file crate.
Don't reach for it for everything. If your crate is tiny, just put items at the root. Re-exports cost nothing at runtime but add a layer of indirection in the docs and source that only pays off when you have something to flatten.
For internal modules used only by your own code, plain use is enough. Save pub use for what should be visible to the outside world.
Where to go next
Module organization in Rust is a topic with surprising depth, and pub use is one of the more useful tools in the toolbox.
How to Organize a Large Rust Project into Modules