Skip to content

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

Hard Framework

Model public item paths and re-exports

Implement a small model of externally reachable item paths. The model is inspired by Rust's module visibility and pub use, with the exact rules below. It does not perform full Rust name resolution or privacy checking.

pub fn public_api(
    modules: Vec<(String, String)>,
    items: Vec<(String, String)>,
    reexports: Vec<(String, String)>,
) -> Vec<String>

Inputs

modules and items contain (path, visibility) pairs. Visibility is one of "pub", "pub(crate)" or "private". Declaration paths are unique within each list. Paths use :: between nonempty names and start at crate; all required ancestor modules and re-export sites are declared. The root "crate" is implicit and always externally reachable.

Each re-export is (containing_module, target_item_path). It introduces the target's final name in the containing module. For example, re-exporting crate::inner::Thing at crate introduces crate::Thing. Re-exports only target declarations in items, not modules or other re-exports. Ignore targets absent from items.

Output rules

Return a sorted list of distinct strings, containing item paths and error markers. Do not include module paths or the root by themselves.

  1. A module is externally reachable if it is pub and every ancestor between it and the root is also pub. Both other visibility labels block this route.
  2. Include an item's original path when the item is pub and its containing module is externally reachable.
  3. For each re-export with a known target, inspect the target's own visibility. If it is not pub, include "E0364:<target>". Include this marker even if the re-export site is private or has an unreachable ancestor.
  4. Otherwise, include containing_module::target_leaf only when the site is externally reachable. For this model, the target's ancestor visibility does not prevent that re-export.

Deduplicate both paths and markers, then sort them together using Rust string ordering. A repeated re-export adds no duplicate output. If different targets produce the same output path, include that string once; this model does not report name collisions.

For example, a public crate::inner::Thing inside a private crate::inner, re-exported at the root, produces ["crate::Thing"]. If inner is public instead, the output is ["crate::Thing", "crate::inner::Thing"]. With no items and no known re-export targets, the output is empty.

How this relates to Rust

A public re-export can provide a convenient path while keeping an implementation module private. Here is a complete library file:

mod engine {
    pub struct Engine;

    pub(crate) mod parts {
        pub struct Bolt;
    }
}

pub use engine::Engine;
pub use engine::parts::Bolt;

If the crate is named facade, a separate binary can use it like this:

fn main() {
    let _engine = facade::Engine;
    let _bolt = facade::Bolt;
}

External code cannot use facade::engine::Engine, because engine is private. The library root can access parts because it is pub(crate). Making parts private would prevent that root-level use from traversing it. Real Rust checks whether the path is accessible at the re-export site; the exercise deliberately omits that check and applies output rule 4 instead.

Similarly, E0364:<target> is this exercise's fixed marker for a non-public target, not a prediction of every Rust diagnostic. Actual diagnostics depend on the kind of item and the inaccessible part of the path. For example, re-exporting a restricted function can produce E0364, while a restricted type can produce E0365. An inaccessible module path can produce E0603.

A public re-export does not make a restricted target public. A public item in a private module is a different case: the item is already pub, but its original path is hidden from external code. An accessible re-export can supply a new path.

If both paths are public, callers may rely on either. Reorganizing implementation modules can preserve those paths through re-exports, but compatibility also includes signatures, trait implementations and documented behavior. Reachable paths are only one part of an API contract.

Repair the starter

The first loop checks only the immediate containing module. It needs to check all ancestors up to the root. The re-export loop currently inserts every target name, including missing targets and restricted items. Apply the rules in order, then let the existing BTreeSet handle sorting and duplicate removal.

Your solution must compile, match the outputs and pass Clippy with warnings treated as errors.