Skip to content

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

Medium Primitives

Three namespaces: types, values, and macros

A namespace is a separate table of names. Two declarations conflict only when they put the same name into the same table, in the same scope. Rust keeps several such tables. Three of them hold the names of items (the things you declare at module level), and they explain a group of rules that otherwise look arbitrary.

namespace what it holds (the parts used in this exercise in bold)
type modules, structs, enums, unions, traits, type aliases; also built-in types such as i32 and generic type parameters
value functions, constants, statics, and the constructors of unit and tuple structs; also local variables and enum variant constructors
macro macro_rules! macros; also procedural macros (function-like, attribute and derive) and built-in attributes

Rust also has a lifetime namespace ('a) and a label namespace ('outer: on a loop). Neither can hold an item name, so this exercise leaves them out.

Some consequences:

  • A module and a function may share a name. mod foo {} and fn foo() {} in the same module compile, because one name is in the type namespace and the other is in the value namespace.
  • A module and a macro may share a name. The standard library has both a std::vec module and a vec! macro.
  • The ! in println!("hi") marks a macro call, and the name is then looked up in the macro namespace. A function you write called println does not conflict with it; println!(...) and println() call different things.

Which structs occupy two namespaces

A struct always puts its name in the type namespace. Whether it also puts the name in the value namespace depends on its shape:

  • Braced struct, struct Foo { x: i32 } or struct Foo {}: type only. You build one with Foo { x: 1 }, which is special syntax, not a value called Foo.
  • Unit struct, struct Foo;: type and value. The bare expression Foo is the one value of that type, so the name Foo must exist as a value.
  • Tuple struct, struct Foo(i32);: type and value. The value Foo is a constructor function that takes an i32 and returns a Foo. That is why Foo(3) looks like a function call, and why you can write .map(Foo).

So these two pairs behave differently:

struct Foo {}   fn Foo() {}   // compiles (with a naming-style warning)
struct Foo;     fn Foo() {}   // error[E0428]: the name `Foo` is defined multiple times

A realistic version of the second line:

struct Config;                    // puts `Config` in the type and value namespaces
fn Config() -> Config { Config }  // E0428: `Config` is already a value

The headline only says the name is "defined multiple times". The note under it says which table was involved: "Config must be defined only once in the value namespace of this module". The unit struct had already claimed the value name.

Related compiler errors

The compiler uses different error codes depending on where the conflicting names came from. All three below report "the name X is defined multiple times":

  • E0428: two items declared in the same module put the same name in the same namespace. Rename one of them.
  • E0255: a use import brings in a name that an item in the same module already declares in the same namespace, as in use std::fmt::Result; struct Result;. Rename the import with as, or rename the item.
  • E0252: two use imports bring in the same name in the same namespace, as in use std::fmt::Result; use std::io::Result;. Rename one with as.

Glob imports (use a::*;) are treated differently. If two globs both provide a name, there is no error until code actually uses that name; then the compiler reports E0659, "S is ambiguous".

The task

pub fn collisions(decls: Vec<String>) -> Vec<String>

Each string is a simplified declaration. Treat all of them as items declared in one module. Return every name that is declared two or more times in the same namespace, sorted, with no duplicates.

form namespaces it occupies
struct Name {} type
struct Name; type and value
struct Name() type and value
enum Name, trait Name, type Name, union Name, mod Name type
fn Name, const Name, static Name value
macro_rules! Name macro

These forms are shorthand, not complete Rust. struct Name() stands for a tuple struct (real code would be struct Name(i32); or similar), and fn Name stands for a whole function.

The macro_rules! simplification. In real code, a plain macro_rules! definition is scoped by its position in the source, and a second definition with the same name shadows the first instead of causing an error. The exercise instead counts two macro_rules! Name lines as a collision. That matches what happens when both are marked #[macro_export], which gives each one a path at the crate root: the compiler then reports E0428 for them.

Contract

  • A recognised declaration is a keyword followed by a name, separated by whitespace. macro_rules! includes its !. A struct's name is followed by exactly one of the suffixes {}, ; or (); spaces before or inside the suffix do not matter, so struct Foo {}, struct Foo{} and struct Foo { } are the same declaration. Struct names consist of ASCII letters, digits and underscores, and all names in the tests do. No test puts whitespace inside a name.
  • Any string that does not match one of the forms is ignored. Examples: an empty string, struct Name with no suffix, fn with no name, or a non-struct keyword followed by more than one word.
  • Names are compared exactly, so log and Log are different names.
  • A name collides when its count in any one namespace reaches two. A unit or tuple struct adds one to both its type count and its value count, so struct Foo; plus fn Foo collides in the value namespace only.
  • Return each colliding name once, even if it collides in more than one namespace or appears three or more times.
  • Sort by byte order (the ordering of Rust's String), so uppercase ASCII letters come before lowercase: ["Zeta", "log"].
  • An empty input, or one with no collisions, returns an empty list.