use Names a Unit of Code and Brings Nothing InStatus: Superseded by ADR-286 — its D1-D5 are D10-D14 there
Related: ADR-047 D1 (which unit use names: a package, not a
file — it moves this record’s boundary and leaves its rules standing),
ADR-041 D1 (the “one form, not two spellings of it” rule this
decision is taken on), ADR-030 D1 (use std::fs, the one use
with a path in it), ADR-011 D2 (name-for-name lowering, which is
why a qualified name reaches the emitter as written).
use pool made a unit of code reachable, and nothing said whether it did
anything else. The question is not decorative: it decides, for every name in
every file, whether a reader can tell where that name comes from without leaving
the line.
It became urgent for a smaller reason. A qualified type name did not resolve to the type its module’s own function handed back, so a module could offer behaviour and not data (#229’s cross-file type defect). Repairing that meant deciding what the qualified form is, because the repair is different if a name can also arrive unqualified.
use makes a unit reachable, and does nothing else.Every name from it is written with its prefix, at every use.
use pool
use request_handling as rh
fn handle(c: pool::Conn) -> rh::Response { // in a type
let d: pool::Conn = pool::make() // in a type and in a call
let e = pool::Conn(id: 1) // in a struct literal
let s: Shared[pool::Conn] = pool::make() // nested, like any other type
return rh::ok(d.id + e.id)
}
The prefix falls where a name is written, not where a value is used. d.id
and d.respond(200) carry none: there is a value in hand and nothing to resolve.
In a body that works with values the prefix barely appears, which is what makes
this bearable without an import form.
error: names are not brought in; a unit is reached through its name
1 | use pool::{Conn, Pool}
^^^^^^^^^^^^^^^^^^^^^^
= write `use pool`, and `pool::Conn` where you need it
= if the prefix is long, `use pool as p` shortens it once, in one place
The blanket form is regretted everywhere it exists — Rust lints it, Python’s
style guide forbids it, Elm discourages it for libraries — and that settles
use pool::* on its own.
The selected form is the real question, and it is refused on a different ground. It is everywhere (Java, Python, Rust, Haskell, Elm, OCaml) and withdrawn nowhere, so it is not a mistake and this record does not claim it is one. Qualified-only is also sustained: Go has had no import selection for fifteen years with no serious movement to add it. One community that had both converged away from the convenient form — Haskell’s practice moved toward explicit lists and qualified names, for the reason this decision is about.
So general experience does not decide it: both work. What decides it is a rule
this language has already applied to itself — one form, not two spellings of
it (ADR-041 D1 withdrew the automatic lambda argument names on
exactly that ground). A selected import creates that situation for every name in
the language: the same type spelled pool::Conn here and Conn there, both
correct. Having just paid to remove one such pair, the language should not
introduce another across its whole surface.
And the refusal is not irreversible, which is why it can be firm. Adding a selected form later breaks no program: everything written with a prefix stays valid. If a program of real size shows the prefix to be a genuine cost rather than a predicted one, the form can be added then, with evidence instead of a guess. What is not deferred is the decision — a question left open is one that every file written in the meantime has to live with unanswered.
use pool as p shortens the prefix, once, in one place.The one thing this answer adds rather than refuses, and it is what makes D2
affordable. It also settles a collision: two libraries that both want to be
http are the consumer’s to name apart, which is authority the consumer needs
anyway (ADR-047 D2’s manifest key rests on it).
pool::Conn without use pool is refused, so that a file still lists what it
depends on at the top. Without this the use line becomes optional decoration
and the dependency list stops being one.
Two units that end up under the same name — by alias, or by collision — is an error rather than a rule about which of them wins. A rule about which wins is a rule a reader has to know to read a line.
Re-export, and that is deliberate: there is no way here for a unit to offer a name that another unit declares. ADR-047 D1 removes the question rather than answering it — inside a package the names already share a space, so there is nothing to re-offer.
Part I 9.1 gains the alias and the two rules, and the refusals are three new diagnostics rather than parse errors: a message that names the way out is the whole difference between a rule and an obstacle (Part III, C.2).
A qualified name reaches the emitter as written, which is what makes this
cheap: the whole path is interned as one name, so pool::Conn in Nikaia is
pool::Conn in Rust and nothing resolved it (ADR-011 D2).
The cost is a prefix at every written name, and it lands where this language
already asks for a type: a signature, and the annotated let that is the only
place sharing begins (Part I, 6.2). What is bought is that a reader of any line
knows where every name in it comes from without consulting the top of the file,
and that no edit elsewhere changes what an already-written line means.
D1, D4 and D5, and one of D2’s three forms. What made them reachable is
ADR-047 D2: until a package could be depended on, every use but
std’s was refused for the simpler reason that there was nothing to name.
D1’s qualified form. A qualified type name resolves to the type its package
declares, a qualified struct literal (http::Request(path: "/x")) parses and is
checked field by field, and a qualified call resolves against the package’s own
ledger entries.
D2, all three forms. A path that is not std’s — use http::server — is
refused with “use names one package and brings no name in”, and the message
names use http and the qualified spelling. The braced and glob forms get
their own sentences, in the grammar, beside the fn: … refusal
ADR-022 already put there — both are in every language that has them,
so a reader will write one.
Two things that arm needed. It comes first in the rule, because the plain arm
matches use http and leaves the rest to fail as the next item — at the same
place, with nothing to say. And it peeks the brace rather than consuming it: a
fail reports at the position it runs at, and consuming the brace first put that
after the implicit whitespace, which had the caret under the line below.
D4, a prefix must be introduced. use http where no dependency is called
http is refused, and the message says where a package is declared — which is
D4 read from the other side: an introduction that names nothing.
D5, one name per file. Two use lines under one name is refused, and it is
checked before reachability: a name written twice is a fact about the file and
says nothing about whether either resolves, so asking about reachability first
would answer a file with two unknown names by naming one of them.
D3, the alias. use http as h shortens the prefix, and a call, a type and a
struct literal all reach through it.
It is resolved away rather than emitted as a Rust use … as …. Every file’s
items are in one crate root (ADR-047 D1), so a Rust alias there
would be the whole program’s name for the package rather than this file’s. The
resolving happens in one place — Parsed::unaliased, on the way to every name —
because the alternative was the same lookup at fourteen call sites in three
modules, where the fifteenth would have been the one that forgot. It is on
Parsed because an alias is a name local to one file, so resolving it is not name
resolution in ADR-011 D2’s sense: it is reading the file’s own
dictionary, and the dictionary is part of what was parsed.
A diagnostic names the package, not the alias. “http::Request.method is
private to http” where the file wrote h::Request: the type’s name is the
package’s whatever one file calls it, and it is the package that keeps a name
private. The use line that connects the two is at the top of the same file.
And D5 counts the alias, which is what makes D3 and D5 one rule rather than
two: the alias is the name this file introduces, so use http as h beside
use https as h is exactly the collision D3 exists to let a consumer fix.