Skip to content

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

Medium Primitives

Resolve names in a simplified import model

Implement a small resolver that selects bindings by priority and reports conflicts within the winning group. This is a simplified model inspired by Rust imports, not an implementation of Rust's full name-resolution rules.

pub fn resolve(bindings: Vec<(String, String, String)>, name: &str) -> String

Each binding contains (kind, bound_name, target_path). For this exercise, use these priorities, from highest to lowest:

item > use > glob > prelude

For the requested name:

  1. Find the highest-priority kind with at least one matching binding.
  2. Collect its distinct target strings. Identical targets count only once.
  3. Return the target if there is exactly one, or "ambiguous" if there are several. Do not consult lower-priority kinds after finding a match.
  4. Return "unresolved" if none of the four kinds has a matching binding.

Match kinds and names exactly, including case. Ignore unrecognized kinds and bindings for other names. Input order does not affect the result. Compare target paths as strings: do not parse paths, follow aliases or normalize their spelling. An empty target string is still a target.

For example, two glob bindings for Config with targets a::Config and b::Config produce "ambiguous". Adding a use binding for Config with target app::Config makes the result "app::Config". Conflicting lower groups no longer matter. Repeating the same use binding changes nothing.

The harness calls resolve for each entry in names and returns the results in that order. An empty names list produces an empty output list; an empty bindings list makes every requested name unresolved.

How this relates to Rust imports

A glob such as use tools::*; imports names accessible from the current scope. Explicit imports can disambiguate competing glob imports. Here is a complete example:

mod left {
    pub const VALUE: i32 = 1;
    pub const LEFT_ONLY: i32 = 10;
}
mod right {
    pub const VALUE: i32 = 2;
    pub const RIGHT_ONLY: i32 = 20;
}

use left::*;
use right::*;
use left::VALUE;

fn main() {
    assert_eq!(VALUE, 1);
    assert_eq!(right::VALUE, 2);
    assert_eq!(LEFT_ONLY + RIGHT_ONLY, 30);
}

Remove the explicit use left::VALUE; and the use of VALUE becomes ambiguous, producing E0659. The two glob declarations alone do not require choosing one VALUE.

Where the model differs

A local item does not simply override an explicit import with the same name in the same namespace. Rust can reject that combination as a duplicate definition, such as E0255. The item > use rule above is a rule of this exercise. Rust also distinguishes namespaces and nested scopes, which this input format does not represent.

Likewise, Rust tracks the identity of imported items. Two differently spelled paths may refer to the same item through re-exports. This exercise uses exact target-string equality as a substitute for that identity.

Public glob re-exports can produce the ambiguous_glob_reexports warning when different items share an exported name. Do not assume one declaration wins silently. Explicit re-exports or aliases make the intended public name clear.

Glob imports can be convenient in a test module or a small prelude. Explicit imports make a name's source easier to see. Choose deliberately, especially for public APIs where adding a new exported name can create a conflict.