Skip to content
beginner Phase 9 · Modules, Crates, and Cargo Workspaces

use Paths and Aliases

Bring items into scope with absolute and relative paths and rename imports.

35m
0 problems
Topic Progress 0%

crate, self, super: The Three Keywords That Anchor a Path

Every path in Rust is either absolute (starts at the crate root) or relative (starts from the current module), and use statements are where you commit to one of them:

// src/main.rs — crate root
mod billing {
    mod taxes {
        use std::collections::HashMap;        // external crate names need no `crate::` prefix
        use crate::billing::Invoice;          // absolute: starts at `crate`
        use super::Invoice as Inv;            // relative: parent module's Invoice, renamed
        use self::rates::VAT;                 // relative: a child of THIS module
    }
}

The three leading keywords are the whole anchoring vocabulary:

Prefix Resolves from Typical use
crate:: the current crate's root internal cross-module imports
self:: the current module a child declared in this file
super:: the parent module reaching up one level
(a plain identifier) whatever is already in scope std, a locally defined module, an import

Note the trap in the example: use std::collections::HashMap; works because std (and external crate names) are in scope at the crate root, so the plain identifier std resolves there — but for your own modules, an unqualified name is resolved relative to the current module's items first. The dependable habit for internal code is use crate::path::Thing;: it reads identically from any module, never breaks when the file moves to a different depth, and makes the origin of every import obvious to a reviewer. Pre-2018 editions allowed paths without crate::, which is why older tutorials show bare use billing::Invoice; — if you see that in code today, it either targets an external crate or is being compiled as edition 2015.

Two rules govern whether a use compiles:

  1. The target must be visible. use creates a local alias, not a privilege: use crate::billing::secret_helper; fails if secret_helper is private to billing. Visibility (previous topic) is checked at the definition; use merely names what you may already name.
  2. One name, one meaning in a scope. Importing Foo from two modules is error[E0252]: the name Foo is defined multiple times — which is what the as alias exists for, and the subject of the next chapter.

use also scopes — an import inside a module applies to that module and its descendants, an import inside a function applies only to that function, and an import inside a mod affects only that block. That locality is why moving code into a helper module often produces a burst of unresolved import errors: the use line that made the name resolvable stayed in the file you moved it out of. Fix by re-declaring the import where the code now lives, or by promoting the item to crate:: and importing it there.

Names live in parallel namespaces — types, traits, and macros — so the same name can exist once in each without colliding. use std::fmt::Result; (a type alias) coexists with use crate::my_result;, and #[derive(Debug)] resolves in the macro namespace even where a type called Debug exists. When an error claims a name is "defined multiple times," it is telling you which of the namespaces got a second occupant — usually fixed with as, occasionally with a qualified path at the single use site.

Finally, use never affects behavior — it only affects names. Nothing gets linked differently, no code is executed, and Rust 2018+ needs no extern crate std; (that line survives only for #[macro_use] on old crates). If you can see use as "a compile-time nickname registration for this scope," the rest of the topic is bookkeeping.

Grouping, Aliasing, and the Deliberate Glob

A file's header is a summary of its dependencies, and Rust's use grammar lets you write that summary compactly with nested use trees — one common prefix, several leaves:

use std::{collections::{HashMap, BTreeMap}, fmt::{self, Display}};
use crate::billing::{Invoice, taxes::{rate, VAT}};
use tokio::{task::JoinHandle, sync::mpsc};

Read {a::{b, c}, d} as "bring in a::b, a::c, and d" — the braces group, the nested braces recurse, and fmt::{self, Display} imports the module itself alongside one of its members (without self you get only Display). The payoff is that a reader scans three lines instead of nine, and a merge conflict on an import touches one group rather than seven adjacent lines.

Renaming handles collisions and intent:

use std::fmt::Result as FmtResult;      // coexist with the crate's own Result alias
use tokio::task::JoinHandle as Task;    // shorten a noisy name
use serde::Deserialize as De;           // conventional shorthand in derive-heavy files

as creates another local alias — it does not change the original item, and both names can exist in the same scope if you need them. When two different items share a name and you only need one, the import is simply the winner; when you genuinely need both, alias one, and when neither import is wanted, fall back to fully qualified paths (std::fmt::Result) at the two use sites.

Traits deserve their own note: a trait must be in scope for its methods to be callable. file.read_all() fails with "read_all is not an item of trait Read... trait Read must be in scope" until use std::io::Read; appears — a rule that surprises anyone coming from languages where methods are looked up on the type. It also explains why impl Trait for Type blocks and method calls sometimes need an import you did not expect: the trait name is a name, and names come from use. Derives count too: #[derive(Serialize)] is a macro path, so a file using it needs use serde::Serialize; (or the fully qualified #[derive(serde::Serialize)]) — the same resolution rules, different namespace.

The cost side of imports is linted rather than ignored. warning: unused import: HashMap`` means exactly one thing: delete the line; cargo fix applies them en masse. There is even a lint for a use that adds nothing (single_component_path_imports flags use foo; where only the crate-root name is used) and one that flags imports shadowed by a glob. Treating those warnings as deletions keeps the header honest — an import list that lies about what a file needs is worse than a long one, because the next reader trusts it.

The glob form imports everything a module exposes:

use std::io::*;      // brings every public item in std::io into this scope

Use it deliberately and rarely: * in tests (use super::*; to reach the private items of the module under test — a standard unit-test idiom) and in small prelude-like modules is idiomatic; at the top of general code it invites ambiguity errors that appear the moment a new public item shows up in the target module, and it makes the file's dependencies unreadable. The compiler helps: unused imports produce warning: unused import: ... on cargo build, and a stray glob produces unused import: ...::* or glob shadowing notes — treat every one of those warnings as a line you can delete.

Where a `use` Belongs: Function-local Imports, Re-exports, and Edition Hygiene

Placement changes meaning, and there are exactly four sensible places for an import.

Module header — the default. Names the module's vocabulary, visible to everything in the file and its children. Aim for a short, alphabetical, grouped header; that block is the file's dependency summary.

Function or block — for one-off or deeply qualified names:

fn validate(record: &Record) -> Result<(), String> {
    use std::net::IpAddr;              // only this function needs it
    record.addr.parse::<IpAddr>().map(|_| ()).map_err(|e| e.to_string())
}

Block-scoped imports keep headers clean when an item is used once, and they let a local type alias (use self::generated::Row as DbRow;) sit next to the code it explains.

Trait impls and test modules — imports inside impl blocks work (the trait must be visible for the impl Trait for Type line) and #[cfg(test)] mod tests { use super::*; } is the near-universal pattern: the test module is a child of the module under test, so super::* brings private helpers, structs, and functions into reach without making them public.

Re-export (pub use) — the fifth job of a path, where an import becomes API:

// lib.rs
pub mod engine;
pub use engine::{Config, Engine};     // users see mylib::Config

Unlike plain use, pub use is reachable from outside the crate, which is how a library flattens a deep internal structure into a two-name public surface — the technique covered in Modules and Visibility, and the reason serde::Deserialize can live in serde::de while nobody writes that path.

Edition hygiene is the last piece: Rust 2015 required extern crate foo; to link a dependency and allowed use foo::bar; for internal modules regardless of position; 2018+ dropped extern crate for ordinary dependencies and made paths uniform — use paths must start with crate::, self::, super::, or a crate/macro name — which is exactly why bare use billing::Invoice; inside a module now fails and the compiler suggests crate::billing::Invoice. When you port old code, the fixes are mechanical: delete extern crate, prefix internal imports with crate::, and let cargo fix --edition do it for you before flipping edition = "2021" in Cargo.toml.

The interview summary worth memorizing: use is a scoped alias, not an include — it must respect visibility, it can be absolute (crate::) or relative (self::, super::), as renames to avoid collisions, nested braces group common prefixes, and pub use turns an internal path into public API.

The five diagnostics this topic produces, and the one-line fix for each — worth memorizing because they account for most early compile errors:

Error Meaning Fix
E0432: unresolved import the path does not exist or is not visible check spelling, add crate::, or raise visibility at the definition
E0252: the name is defined multiple times two imports claim one name as alias, or keep only the one you use
E0603: ... is private the target exists but is not reachable from here pub / pub(crate) at the definition
E0659: ... is ambiguous a glob and an explicit import both provide a name qualify the path explicitly at the use site
warning: unused import nothing in this scope references the alias delete it (cargo fix does it for you)

Notice they all reduce to one of three questions: does the path exist, is it visible, and is the name unambiguous? Debugging an import error fast means asking them in that order.

Quiz

1. Inside `crate::billing::taxes`, what does `use super::Invoice;` refer to?

Question 1 options

2. You import the same name from two modules and get `error[E0252]: the name is defined multiple times`. The cleanest fix?

Question 2 options

3. Why does `data.read_to_end(&mut buf)` fail with "`read_to_end` is not an item of `trait Read`" until you add `use std::io::Read;`?

Question 3 options

4. What is the effect of `pub use engine::Config;` in `lib.rs`?

Question 4 options

Flashcards

Question

What are the three path-anchoring keywords in a `use` statement, and what does each start from?

Answer

`crate::` → the current crate's root; `self::` → the current module; `super::` → the parent module. A plain leading identifier resolves from the current scope (external crates and locally defined names). Prefer `use crate::...` for internal imports so moving files never breaks the path.

Question

What does `use std::fmt::{self, Display};` import, and why is `self` needed?

Answer

It imports the module `std::fmt` itself *and* the item `std::fmt::Display`. Inside a use tree, braces group sibling paths under a common prefix and `self` names the prefix itself — without it only `Display` is imported.

Question

When must a trait be imported, and what breaks without it?

Answer

Before calling its methods or implementing it by name: trait methods are only callable when the trait is in scope (`use std::io::Read;`). Without it the compiler says "`method` is not an item of `trait X`... trait must be in scope." Inherent methods on the type need no import.

Question

`use` vs `pub use` — one sentence each.

Answer

`use` creates a private local alias for readability — it cannot grant access to anything you couldn't already name. `pub use` re-exports the item under a new path as part of the crate's public API, which is how libraries flatten deep internals into short stable names.

Question

Where should a `use` go, and what is the `use super::*;` test idiom?

Answer

Module header by default; function/block for one-off or deeply qualified names; inside `#[cfg(test)] mod tests` for helpers. `use super::*;` in the test module (a child of the module under test) brings private functions and structs into reach without making them public.

Revision Notes

Key Takeaways

  • 1. Paths are absolute with `crate::` or relative with `self::`/`super::`; unqualified names resolve from the current scope, which is why `use crate::...` is the stable habit for internal imports
  • 2. A `use` creates a local alias and never grants access — visibility at the definition site is checked independently, and duplicates collide with E0252 (fixed with `as`)
  • 3. Nested use trees (`{a::{b, c}, d}` with `self` for the module itself) compress headers into a readable dependency summary without changing semantics
  • 4. Traits must be named in scope before their methods resolve, and `use super::*;` is the standard way for a child test module to reach private internals
  • 5. `pub use` is the fifth job of a path: it publishes a re-export as public API, flattening deep internals into short stable names that survive refactors

Interview Tips

  • • "\"Absolute vs relative paths?\" — `crate::` from the root, `self::` current, `super::` parent, bare names from scope; add that `use` is an alias that cannot outrun visibility"
  • • "\"What does `fmt::{self, Display}` mean?\" — import the module and the item together; nested braces group under one prefix; then mention `as` as the fix for E0252 collisions"
  • • "\"Why can't I call `.read_to_end()`?\" — trait methods need the trait in scope; show `use std::io::Read;` and contrast with inherent methods"
  • • "\"How do I expose a clean API from deep internals?\" — `pub use` re-exports in `lib.rs`: users type `mylib::Config` while the definition stays in `engine`, so you can move internals without a breaking change"

Cheat Sheet

use Paths and Aliases

Anchors:

use crate::billing::Invoice;      // absolute: from the crate root (preferred for internal code)
use self::rates::VAT;             // relative: child of this module
use super::Invoice as Inv;        // relative: parent module, renamed
use std::collections::HashMap;    // external crate names resolve without a prefix

Grouping and renaming:

use std::{collections::{HashMap, BTreeMap}, fmt::{self, Display}};
use serde::Deserialize as De;             // E0252 duplicate-name fix
use tokio::{self, task::JoinHandle};      // module + item in one tree

Placement:

Site Use it for
module header the file's vocabulary (grouped, sorted)
fn / block one-off or deeply qualified names
#[cfg(test)] mod tests use super::*; — private items stay testable, still private
pub use publish a short stable path over an internal module

Rules: use is a scoped alias — it cannot bypass visibility; trait names must be imported before their methods work; globs (use super::*) belong in tests/small modules; unused imports are warnings you should delete.

Editions: 2015 needed extern crate and bare internal paths; 2018+ uses uniform paths (crate:: / self:: / super:: / crate name) — port with cargo fix --edition.

Continue Learning

Before You Start

Related Reading