Modules and Visibility

Importing with use

A use declaration creates a local shortcut (binding) in the current scope to a path in the module tree so you can refer to types, traits, functions, or modules by their short name instead of repeating the full path at every use site.

Idiomatic use Conventions by Item Kind

In Rust, how deeply you import a path depends on whether the target is a type/trait or a function:

Item Kind Idiomatic use Granularity Example use Call-Site Appearance Why
Structs, Enums, Traits Import the item itself use std::collections::HashMap; let map = HashMap::new(); Type names are UpperCamelCase and self-descriptive
Functions Import the parent module use std::env; let args = env::args(); Shows that args() comes from env rather than being a local function
Enum Variants Usually qualify with Enum::Variant, or import directly when unambiguous use Status::{Active, Idle}; Active Avoids repetition in large match blocks
Trait (for methods only) use Trait as _; (anonymous import) use std::io::Write as _; buf.write_all(b"hi") Unlocks trait methods without polluting the type namespace with the name Write

Grouping Imports with Nested Paths ({...}) and Glob Imports (*)

  1. Nested path lists (use path::{A, B, sub::C};): Combine multiple imports sharing a common prefix into a single declaration.
  2. Self-import in a group (use std::io::{self, Read};): Imports both std::io and std::io::Read.
  3. Glob import (use path::*;): Brings all public items of path into scope. Because * can make it unclear where a name was defined, reserve glob imports for test modules (use super::*;) and crate preludes (use my_crate::prelude::*;).
use std::collections::{BTreeSet, HashMap};
use std::fmt::{self, Write as _};

fn format_scores(scores: &HashMap<&str, u32>) -> Result<String, fmt::Error> {
    let mut sorted_names: BTreeSet<&str> = scores.keys().copied().collect();
    sorted_names.insert("bonus");

    let mut out = String::new();
    for name in sorted_names {
        let val = scores.get(name).copied().unwrap_or(0);
        // `.write_fmt` (via `write!`) is available because `std::fmt::Write as _` is in scope:
        write!(&mut out, "{name}={val} ")?;
    }
    Ok(out)
}

fn main() {
    let mut map = HashMap::new();
    map.insert("alice", 95);
    map.insert("bob", 88);

    println!("{}", format_scores(&map).unwrap());
}
alice=95 bob=88 bonus=0 

Scoping Rules for use Declarations

A use declaration is scoped to the module or block in which it appears:

  • A use at the top of src/main.rs is in scope for src/main.rs, but is not automatically in scope inside a child mod sub { ... } unless that child module writes its own use (or use super::*;).
  • A use can even be placed inside an individual fn body to keep an import local to that single function:
#[derive(Debug)]
enum TrafficLight {
    Red,
    Yellow,
    Green,
}

fn next_light(current: TrafficLight) -> TrafficLight {
    // Scoped `use` inside a function body so variant names don't pollute the module:
    use TrafficLight::{Green, Red, Yellow};

    match current {
        Red => Green,
        Green => Yellow,
        Yellow => Red,
    }
}

fn main() {
    let light = next_light(TrafficLight::Red);
    println!("Next light: {light:?}");
}
Next light: Green