Skip to content

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

Medium Primitives

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

A use declaration adds a name to the scope of the module or block it appears in. The name is an alias for a path such as std::collections::HashMap. It does not load, run or link any code; it only decides which short names the code around it can write. This exercise asks you to work out exactly which names one use declaration creates.

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

Parse one use declaration and return every (full_path, bound_name) pair it creates, sorted. full_path is the complete path the name refers to, and bound_name is the name it is available under. Malformed input and glob imports return 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", "_")]

In the first example the name is the last segment of the path. In the second, the outer braces share the prefix std::collections, the inner braces share std::collections::hash_map, and self names that inner prefix itself, so one line creates three names. The third example is explained below.

The grammar

  • A path is one or more segments separated by ::, such as std::io::Write.
  • A leaf is a path, optionally followed by as name. Its bound name is the alias if there is one, otherwise the path's last segment.
  • A group is prefix::{ ... }, a comma-separated list of items inside braces. Each item is itself a leaf or a group, and the prefix is put in front of every item. Groups can nest to any depth, so use std::{fmt, io}; creates two names. The prefix may be left out entirely, as in use {std::fmt, std::io};, which creates the same two names.
  • self as an item of a group stands for the group's prefix. In hash_map::{self, HashMap}, self binds the module hash_map under the name hash_map (or under its alias, as in {self as m}), and HashMap binds the type.
  • as _ imports an item without giving it a usable name. This exercise reports that as the bound name "_".

What as _ is for

To call a trait method with method-call syntax on a concrete type, the trait has to be in scope. This does not compile without the use line:

use std::io::Write;

fn greet(buf: &mut Vec<u8>) -> std::io::Result<()> {
    buf.write_all(b"hi") // error[E0599] without `use std::io::Write`
}

Vec<u8> implements std::io::Write, but write_all is a trait method, and the compiler only looks for trait methods in traits that are in scope. Traits in the prelude (such as Clone and Iterator) are always in scope. You also do not need the import to call a method on a generic parameter bounded by the trait (W: std::io::Write) or on a dyn std::io::Write.

Importing Write this way also takes the name Write in that scope, which can clash with another item called Write, such as std::fmt::Write. use std::io::Write as _; brings the trait's methods into scope without taking any name. Several as _ imports can sit in the same scope without conflicting, and code in that scope cannot refer to the trait as Write.

Two lints are relevant here. The default unused_imports warning stays quiet for use std::io::Write; as long as one of its methods is called, because the import is doing work. Clippy's unused_trait_names lint, which is in the allow-by-default restriction group, reports a trait import whose name is never written and suggests as _. Enable it if your project prefers that style.

Similarly, use std::string::String; changes nothing, because String is already in the prelude. unused_imports does not report it, since the name is used; the allow-by-default redundant_imports lint does.

Contract

  • Shape. After trimming leading and trailing whitespace, the input must start with use, followed by at least one whitespace character, then one use tree, then ;. (Rust also accepts use{a, b}; with no space; this parser does not.) Whitespace may appear between tokens. Anything else, for example a missing ;, a second statement, pub use, or text before use, returns an empty vec.
  • Segments. Every path segment is an ASCII identifier: a letter or _ followed by letters, digits or _. _ alone is not a valid segment, and no segment may be empty, so a path cannot start with ::. (Rust itself accepts a leading ::, as in use ::std::fmt;, but this parser rejects it.) The parser does not reject Rust keywords such as crate or fn as names, apart from the self and _ rules here. Returned paths are written with :: and no whitespace, even if the input had spaces around ::.
  • self. In the full path, self may appear only as the first segment (as in use self::config::Settings;). The item self inside a group binds the group's prefix, under the prefix's last segment or under its alias. A self item whose prefix is empty, such as use self; or use {self};, is malformed.
  • Aliases. as takes one identifier, which may be _. An alias applies only to a leaf: use a::{b} as c; is malformed.
  • Groups. Braces must balance, and a group must be the last part of its item: use a::{b}::c; is malformed. A group may be empty (use a::{}; creates no names, so the result is an empty vec) and may end with one trailing comma ({a, b,}). Any other empty item, as in {,} or {a,,b}, is malformed.
  • Globs. A * anywhere means a glob import, which this parser does not handle; return an empty vec.
  • Output. Sort the pairs by full path, then by bound name, comparing strings byte by byte (so uppercase letters sort before lowercase ones). If the same pair would be produced twice, include it once. The parser does not report a name bound twice, whether to the same path or to two different ones; the real compiler rejects both with E0252. Imports with as _ are the exception, since they take no name.