RustArena

Step 5 of 7

Re-exports with pub use

How you organize modules internally is your business. People using your code want short, stable paths. pub use brings a name into a module and makes it public there: a re-export.

mod shop {
    mod catalog {
        pub struct Product {
            pub name: String,
            pub cents: u32,
        }
    }

    mod pricing {
        use super::catalog::Product;

        pub fn total(products: &[Product]) -> u32 {
            products.iter().map(|p| p.cents).sum()
        }
    }

    pub use catalog::Product;
    pub use pricing::total;
}

use shop::{total, Product};

fn main() {
    let cart = vec![
        Product { name: String::from("rope"), cents: 1250 },
        Product { name: String::from("lamp"), cents: 3999 },
    ];
    println!("{} items, {} cents", cart.len(), total(&cart));
    println!("first: {}", cart[0].name);
}
2 items, 5249 cents
first: rope

The standard library does this all the time. std::collections::HashMap is really defined in a private module deep inside std and re-exported where you find it. Crates often offer a prelude module that re-exports their most-used items, so one glob brings them all in: use rayon::prelude::*;. Rust itself has a prelude: that's why Vec, String, Option and Some work without any use.

A plain use is private

A use without pub is a private shortcut for its own module. Others can't go through it:

mod shop {
    mod catalog {
        pub struct Product {
            pub name: String,
        }
    }

    use catalog::Product;

    pub fn make() -> Product {
        Product { name: String::from("rope") }
    }
}

fn main() {
    let p: shop::Product = shop::make();
    println!("{}", p.name);
}
error[E0603]: struct import `Product` is private

Notice that shop::make() itself is fine: you can receive a Product without being able to name its type. Making it pub use catalog::Product; fixes the error.

Designing a library's public API

In a library crate, the paths users can reach are the API you promise to keep working. A common layout keeps the modules private and re-exports the important items at the top (in lib.rs), so users write mycrate::Product instead of mycrate::catalog::types::Product. pub use can also rename: pub use v2::parse as parse_v2;.

Your task

The tests use geometry::Circle and geometry::area, but those live in the private modules geometry::shapes and geometry::math. Add re-exports to geometry (don't move the code, and keep the inner modules private) so the tests compile and pass.

Challenge 2

Give the game module a prelude: a public module prelude inside game that re-exports Player and spawn, so that one line, use game::prelude::*;, brings in everything a user needs. The tests do exactly that.

Challenge 3

Your library has an old parser (v1::parse) and a new one (v2::parse, which also accepts spaces around the number and _ separators like 1_000). Users should call api::parse for the new one and api::parse_legacy for the old one. Inside mod api, add re-exports with renaming (pub use ... as ...) where needed. Don't copy the functions.

Previous: Struct fields, privacy and pub(crate) Next: Splitting a crate into files