Skip to content

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

Medium Primitives

Restricted visibility: pub(crate), pub(super), pub(in path)

Implement a visibility checker for direct paths within one crate's module tree. Given item declarations, a viewing module and a target path, return whether that module can access the target through the supplied path.

pub fn visible_from(items: Vec<(String, String)>, viewer: String, target: String) -> bool

Each entry in items is (path, visibility). Modules have entries too. Paths start with crate and use :: between components. Entries are unique, all containing modules below the crate root are present, and viewer names the root or a module in this tree. The root itself need not have an entry. Return false for an unknown target.

Supported visibility strings are private, pub, pub(crate), pub(super) and pub(in crate::...). Restriction paths name valid enclosing scopes, including the declaring module itself. This exercise excludes imports, re-exports, aliases, struct-field access and trait-specific rules.

Turn each visibility into an allowed scope

For an item at crate::a::b::item, its declaring module is crate::a::b. A visibility scope includes the named module and all its descendants.

Visibility Allowed scope before checking ancestor modules
private The declaring module and its descendants.
pub(super) The declaring module's parent and its descendants.
pub(in P) Module P and its descendants.
pub(crate) The current crate.
pub No item-level restriction for viewers in this crate.

Module paths need component boundaries. crate::a::child is inside crate::a, but crate::ab is not. Check equality or a prefix ending in ::, rather than a bare string prefix.

Check every part of the target path

An accessible item can still be hidden behind an inaccessible containing module. Check the target, then each declared ancestor module up to the root. Return false as soon as any visibility rejects the viewer.

For example, suppose crate::outer is public, crate::outer::inner is private, and crate::outer::inner::Thing is public:

  • crate::outer can access Thing: inner is private to outer and its descendants, so the viewing module is admitted.
  • crate::outer::inner::deep can also access it.
  • crate::other cannot access it through that path because inner rejects that viewer.

A private module does not hide its contents from every module outside itself. Its privacy boundary is its declaring module. In particular, a private top-level module is declared at the crate root and is accessible throughout that crate, subject to the visibility of the items inside it.

Understand pub(super) in context

An item declared inside crate::a with pub(super) is visible within crate, including sibling modules such as crate::b. An item declared inside crate::a::nested with pub(super) is instead visible within crate::a. The meaning depends on where the item is declared.

That item-level scope does not bypass the path checks. A more restrictive containing module can still prevent a viewer from reaching the item.

Restriction paths describe enclosing scopes

pub(in crate::a) can restrict an item declared inside crate::a::nested to crate::a and its descendants. It cannot grant access only to an unrelated sibling module. A restriction to a non-ancestor scope is rejected by Rust with E0742; the exercise supplies valid restrictions, so you do not need to diagnose invalid declarations.

Keep the model's limits clear

pub and pub(crate) admit the same viewers in this model because every viewer is in the same crate. They differ for external callers. Full Rust visibility also includes re-exports, which can expose an item through a new public path even when its original module path is private. This checker handles only the paths supplied in items.

A single source file can contain nested modules and demonstrate real privacy errors. Here the declarations arrive as runtime data, so implement their access rules as a boolean function. The harness runs several viewer/target queries over the same item table and collects the answers in order.

Check the target and its ancestors using the same scope predicate. Passing requires compilation, correct query results and Clippy with warnings treated as errors.