Skip to content

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

Medium Primitives

use declarations: nested groups, as, self, and _

A use declaration does one thing: it creates a name binding in the current module. Not an import in the Python sense, not a link step — a local alias for a path. Once you see it that way, nested groups and as _ stop being syntax to memorise.

pub fn expand_use(decl: &str) -> Vec<(String, String)>

Parse one use declaration and return every (full_path, bound_name) pair it creates, sorted. Malformed input, and globs, produce an empty vec.

use std::collections::HashMap;
  -> [("std::collections::HashMap", "HashMap")]

use std::collections::{BTreeSet, hash_map::{self, HashMap}};
  -> [("std::collections::BTreeSet",           "BTreeSet"),
      ("std::collections::hash_map",           "hash_map"),
      ("std::collections::hash_map::HashMap",  "HashMap")]

use std::io::Write as _;
  -> [("std::io::Write", "_")]

The grammar, in four bullets

  • A leaf is a path, optionally followed by as name. The bound name is the alias if there is one, otherwise the last segment.
  • A group is prefix::{ … }. Everything inside inherits the prefix, and groups nest arbitrarily deep. use std::{fmt, io}; is two bindings from one line.
  • self inside a group binds the module the group hangs off: hash_map::{self, HashMap} binds both hash_map (the module) and HashMap (the type). That one line creates three bindings in the example above, which is why people find it confusing.
  • as _ binds the name _, which is to say: it binds nothing you can spell.

Why as _ exists, and why it is almost never taught

To call a trait’s methods, the trait has to be in scope:

use std::io::Write;   // without this, `.write_all()` does not exist
file.write_all(b"hi")?;

This is a genuine cliff for beginners — “the method is right there in the docs, why is it not on my value” — and the answer is always “you did not import the trait”. But now the name Write is taken, and you may well have your own Write, or a second trait from another crate with the same name. as _ brings the methods without the name:

use std::io::Write as _;

The trait is used, so unused_imports stays quiet either way. clippy:: unused_trait_names is the lint that notices the name itself is never mentioned and suggests as _. Two tools, two opinions, both correct — rustc is asking “is this import doing anything?” and clippy is asking “is it doing only the thing you meant?”.

While we are here: use std::string::String; is entirely redundant, because String is already in the prelude. unused_imports will not catch it (the name is used); the allow-by-default redundant_imports will.

Contract

  • A well-formed declaration starts with use followed by whitespace and ends with ;. Anything else returns an empty vec.
  • Braces must balance, and a group must be the last thing in its subtree — use a::{b}::c; is not a thing.
  • Path segments are identifiers. A * anywhere means this is a glob, which is out of scope for this parser (globs get their own item, 10.6) — return an empty vec.
  • A trailing comma inside a group is legal and binds nothing extra.
  • Sort by full path, then by bound name.