How to Re-Export Items with pub use in Rust

pub use re-exports items through the current module, letting you keep internal modules organised while presenting a flat, stable public API to your crate's users.

The refactor that breaks your users

You spent the weekend building a parser library. The code works. You split it into lexer.rs, parser.rs, and ast.rs. Your friend pulls the crate and writes my_parser::lexer::Token::new(). You nod. Then, on Tuesday, you realize the lexer logic belongs inside the parser. You move the file. Your friend's import breaks. You just broke the public API for a refactor that should have been invisible.

This happens when your file structure leaks into your public interface. Users start depending on paths like my_crate::internal::utils::helper. They shouldn't. Your file layout is an implementation detail. It changes as you learn more about the problem. The public API should stay stable.

pub use solves this. It lets you flatten the public face of your crate without flattening the source tree. Internally you keep modules organized by concept. Externally users type my_crate::Token and never see the file structure.

The storefront and the warehouse

Think of your crate as a warehouse. The modules are aisles stacked with inventory, organized the way that helps you stock and pick. Users shouldn't need a map of the aisles. They should walk up to the storefront window and see exactly what you want them to buy.

pub use is the mechanism that moves items from the back room to the display window. It takes an item defined in a private module and makes it available at a public path. The item stays where it is. You just create a new name for it that users can reach.

Keep the warehouse messy if it helps you stock faster. Just polish the window.

Minimal re-export

Here's the smallest case: a private module, a public item inside it, and a re-export that bridges the gap.

// src/lib.rs
mod internal {
    // Helper is public within the crate, so the root can see it.
    // The module itself is private, so users can't reach in.
    pub fn helper() {}
}

// Re-export helper at the crate root.
// Users can now call my_crate::helper().
pub use internal::helper;

Anyone using your crate writes my_crate::helper(). They cannot write my_crate::internal::helper() because internal is not declared pub. The user only sees what you choose to re-export.

Re-exports are aliases, not copies. The compiler treats the path as a synonym.

How the compiler resolves paths

When the compiler sees pub use internal::helper; at the crate root, it adds an entry in the crate's public name table. The entry says: "the name helper, when looked up at the crate root, resolves to the item at internal::helper."

There is no copy of the function. There is no extra code generated. The original definition lives where you wrote it. pub use is purely a name-resolution shortcut.

This means re-exporting is free at runtime. It also means type identity is preserved. If you re-export client::Client as Client, the type my_crate::Client is identical to my_crate::client::Client. You can pass values between the two paths without conversion. No From impl needed. The compiler knows they are the same thing.

Realistic library structure

Here's how a library crate looks in practice. The internal modules are private. The root module curates the public API.

// src/lib.rs

// Internal modules. Declared without pub, so they don't leak.
mod client;
mod request;
mod response;
mod error;

// The public surface. Users import my_crate::Client, not client::Client.
pub use client::Client;
pub use request::{Request, RequestBuilder};
pub use response::Response;
pub use error::{Error, Result};

The user code stays clean.

use my_crate::{Client, Request};

fn main() {
    let client = Client::new();
    let req = Request::builder().build();
    let _resp = client.send(req);
}

The user never types client:: or request::. If you split client.rs into client/transport.rs and client/pool.rs next week, the user's import does not change. You moved files. The re-export in lib.rs still points to the same logical module. The public path stays put.

Refactor internals freely. The public path stays put.

Renaming and disambiguation

Internal names often clash with public expectations. You might have a module called error that defines an Error type. Re-exporting it as Error is fine. But if you also have a ParseError, you might want to expose both without confusing users.

pub use ... as ... handles renaming.

mod internal_error;

// Re-export the generic error with a specific name.
pub use internal_error::Error as ParseError;

// You can also keep the original name if needed.
pub use internal_error::Error as Error;

The renaming is purely surface. The underlying type is unchanged. Two pub use statements with different as names create two paths to the same type. Users can import ParseError or Error; they refer to the identical struct.

Convention aside: use as when the internal name is too generic or when you want to group related items under a clearer public name. Don't rename just to rename. Keep the mapping obvious.

Re-exporting dependencies

You can re-export items from other crates. This is a polite way to spare your users an extra dependency.

// Re-export serde_json::Value so users don't need serde_json.
pub use serde_json::Value;

Now users write my_crate::Value instead of serde_json::Value. They don't need serde_json in their Cargo.toml.

Be careful here. Once you re-export a type from another crate, your public API depends on that crate's version. If serde_json releases a breaking change, your users feel it. You become the gatekeeper for that dependency.

Re-export a dependency only when you're willing to stand in front of it. If the dependency is unstable or changes frequently, let users depend on it directly so they can control the version.

The prelude pattern

Some libraries have a handful of items that every user will want. Importing them one by one gets tedious. The community convention is a prelude module.

// src/lib.rs
mod client;
mod request;

// Collect the most common items in a private module.
mod prelude_items {
    pub use crate::client::Client;
    pub use crate::request::Request;
}

// Expose the prelude publicly.
pub mod prelude {
    pub use crate::prelude_items::*;
}

Users can write use my_crate::prelude::*; to get all the essentials. This is the same trick std plays. use std::prelude::* is implicit in every Rust file and gives you Vec, String, and Option without imports.

Avoid pub use module::*; at the crate root. Glob re-exports there make it impossible for users to tell what your public API is. They can also cause name clashes when you add new items later. Keep globs inside a prelude module where the scope is controlled.

Curate the API. Don't dump the namespace.

Pitfalls and compiler errors

The most common slip is forgetting to mark the inner item pub. pub use can only re-export items that the current module can access.

mod internal {
    fn helper() {} // Private function.
}

pub use internal::helper; // Error.

The compiler rejects this with error[E0603]: function 'helper' is private. The fix is to mark the inner item pub. The module can stay private. The item must be public to the re-exporter.

mod internal {
    pub fn helper() {} // Public within the crate.
}

pub use internal::helper; // Works.

Mark the item public, keep the module private. That's the lock and key.

The second slip is name shadowing. If you re-export two items with the same name, you get a duplicate definition error.

pub use foo::Item;
pub use bar::Item; // Error: name clash.

Rename one of them with as.

pub use foo::Item as FooItem;
pub use bar::Item;

When to use pub use

Use pub use when your internal module structure differs from the desired public API. That is most of the time. It decouples file organization from user experience.

Use pub use ... as ... when the internal name is ambiguous, clashes with another re-export, or doesn't match the public domain terminology.

Use a prelude module when you have a small, stable set of items that every user will import. Keep the prelude curated. Add items only when they are truly universal.

Use pub use on dependencies when you want to shield users from transitive dependency management and you are comfortable inheriting the version coupling.

Reach for direct module paths when the module hierarchy itself is part of the public API design. If you want users to organize their imports by feature, expose the modules as pub mod.

Treat the re-export list as your API contract. Every name there is a promise to your users.

Where to go next