Re-exporting (pub use)
Re-exporting (pub use)
Level 7 — Modules, Visibility & Project Structure Exposes an item from a submodule at a higher level in the module hierarchy.
1. Prerequisites
useStatement — The standard way to create shortcuts, whichpub usebuilds upon.pubVisibility — The keyword that makes things visible to the outside world.modDeclaration — The internal tree structure thatpub usehelps hide from users.
2. Term Category
Rust-specific (the API cleaner): When you build a large library, your internal code is usually highly nested into dozens of folders and files. But you don't want your users to have to type use my_library::network::protocols::http::client::connect;. You want them to just type use my_library::connect;.
Re-exporting (pub use) allows you to grab an item from deep inside your messy internal structure and present it at the very top level of your public API.
3. Explanation
(1) Design Motivation — "Why did we design this?"
The way you organize your code internally for maintenance (lots of tiny files, deeply nested folders) is almost never the way you want your users to interact with it (which should be flat, simple, and clean).
If you force users to navigate your internal module tree, you create two massive problems:
- They have to write horribly long
usestatements. - If you ever reorganize your internal folders, you break everyone's code!
pub use completely decouples your internal file structure from your public API. You can move your files around all you want; as long as you pub use the structs at the root level, the user's code will never break.
(2) Reality Metaphor
Imagine a mega-store like IKEA.
Internally, the logistics team tracks a couch as being located in Warehouse 4 -> Aisle 12 -> Shelf B -> Bin 9. It would be an absolute disaster if customers had to navigate the dark, messy warehouse just to buy a couch.
Instead, IKEA takes the couch from the deep warehouse and puts it right at the front of the store in the Showroom.
That is pub use. You are taking a deeply nested item from your "warehouse" (internal modules) and placing it in a highly visible "showroom" (lib.rs) so users can grab it instantly.
(3) Rust Code Examples
Short Snippet (The Showroom)
Here is what a lib.rs file looks like for a library using pub use.
// 1. We declare our messy internal module structure.
// Users don't want to type `use my_crate::auth::passwords::User;`
pub mod auth {
pub mod passwords {
pub struct User;
}
}
// 2. THE SHOWROOM! We re-export `User` to the very top level.
// Now users can just type `use my_crate::User;`!
pub use crate::auth::passwords::User;
Fuller Example (The User's Perspective)
Here is what it looks like from the perspective of the developer who downloads the library above. They don't even know the auth::passwords modules exist!
// Because the library author used `pub use`, the user gets a beautiful, flat API!
use my_crate::User;
fn main() {
let u = User;
}
4. Common Mistakes & Pitfalls
Mistake 1: Confusing Private use with Public pub use Re-Exports
The mistake: Writing use internal::Engine; at top-level lib.rs expecting callers of the crate to access my_crate::Engine.
Why it is wrong: use brings an item into the local scope of that single file. Callers outside the crate cannot access it. To expose the item publicly at the crate root, write pub use internal::Engine;.
Incorrect:
use internal::Engine; // Private local alias only!
Fix:
pub use internal::Engine; // Re-exports Engine to crate public API!
Mistake 2: Re-Exporting Items from Private Submodules without pub Access
The mistake: Writing pub use internal::Secret; when struct Secret inside internal.rs is private (struct Secret).
Why it is wrong: pub use cannot grant access to items that are not visible to the re-exporting module. Secret must be marked pub or pub(crate).
Mistake 3: Creating Ambiguous Double Re-Exports of Colliding Symbol Names
The mistake: Re-exporting pub use backend_a::Config; and pub use backend_b::Config; into the same root namespace.
Why it is wrong: Downstream callers writing use my_crate::Config encounter ambiguity compiler errors (error: Config is ambiguous). Re-export with aliases via pub use backend_a::Config as ConfigA;.
5. Practice Exercises
Exercise 1: Flattening the API
Scenario: You wrote a crate called database_lib. Your lib.rs currently looks like this. Users are complaining that they have to write use database_lib::storage::postgres::Database;, which is too long.
Modify the code below to Re-Export Database so users can just write use database_lib::Database;.
// File: src/lib.rs
pub mod storage {
pub mod postgres {
pub struct Database;
}
}
// TODO: Add a line here to re-export `Database`!
Answer
Exercise 2: Flattening Module Structures with pub use
Scenario: Re-export a deeply nested function pub use deep::nested::core_action; at top level.
Expected output:
Answer
Exercise 3: Re-Exporting External Types \u2014 The Diamond Problem
Scenario: Re-exporting external dependency types from your library's public API solves a subtle versioning problem called the diamond dependency conflict.
Consider this scenario:
- Your library
my_lib v1.0depends onserde v1.0and re-exportsserde::Serialize. - A user's application depends on both
my_lib v1.0andserde v1.0. - The user wants to implement
Serializefor their struct and pass it tomy_lib.
Write:
src/lib.rsofmy_libthat re-exportsserde::Serializeas part of its public API.- An example showing how the downstream user
uses the re-exported trait (not directly from serde) and implements it for their own struct. - An explanation: why does using
pub use my_lib::Serialize(the re-export) instead ofuse serde::Serializedirectly prevent a compilation error in the user's code?
Expected output:
Answer
(No runtime output — this is a library API design exercise. The key insight is compile-time compatibility.)
-
Hint 1: In Rust,
serde::Serializeat version1.0.0andserde::Serializeat version1.0.1are the same trait (sameCargo.tomlsemver range). Butserde v1.0and (hypothetically)serde v2.0would be different traits — a struct implementingv1::Serializedoes NOT implementv2::Serialize, even if they look identical. -
Hint 2: If
my_libre-exportsserde::Serialize, the user who writesuse my_lib::Serializegets exactly the same trait object as the onemy_libuses internally — they come from the same resolved crate, same version, same type ID. No mismatch possible. -
Hint 3: If
my_libdoes NOT re-exportSerialize, the user must addserdeto their ownCargo.toml. If they pick a different semver-incompatible version,cargo buildmay fail witherror[E0277]: the trait Serialize is not implementedeven though their struct clearly derives it — because two different versions of the trait exist simultaneously.
Implementation
// my_lib/src/lib.rs
// Re-export the trait we use in our public API.
// Users should import Serialize from HERE, not from serde directly.
// This guarantees they get the exact same version we compiled against.
pub use serde::Serialize;
/// Serializes any `Serialize` implementor to a JSON string.
pub fn to_json<T: Serialize>(value: &T) -> String {
// (In a real impl, this would call serde_json::to_string)
format!("\"serialized: {}\"", std::any::type_name::<T>())
}
// user_app/src/main.rs
// Import Serialize from my_lib, not from serde directly.
// This guarantees version compatibility with my_lib's internal usage.
use my_lib::Serialize;
#[derive(Serialize)]
struct User {
name: String,
age: u32,
}
fn main() {
let user = User { name: "Alice".into(), age: 30 };
// This works because User::Serialize and my_lib's Serialize are the SAME trait.
let json = my_lib::to_json(&user);
println!("{}", json);
}
Answer to the "why re-export prevents errors" question:
When my_lib declares pub use serde::Serialize, it exposes the exact instance of the Serialize trait that it compiled against. Any user who imports my_lib::Serialize gets that exact same instance \u2014 they cannot accidentally import a different version. If they had imported serde::Serialize directly with their own Cargo.toml entry, Cargo might resolve a different semver-incompatible version, giving them a different type with the same name. Rust's type system would then correctly reject their type as "not implementing my_lib's Serialize" \u2014 a confusing but technically correct error. Re-exporting closes this gap by making the library the single source of truth for its own dependencies' types.
6. Related Terms
useStatement — The private version of this keyword, which only creates a shortcut for your own internal file.pubVisibility — The requirement for the item being re-exported.
7. Key Takeaways
usecreates a private shortcut for the current file.pub usecreates a public shortcut that external users can see and use.- It allows you to completely decouple your messy internal file structure from your clean, flat public API.
- It is heavily used in
lib.rsto create the "Facade" pattern for Rust libraries.