13.1Naming collisions, and why modules exist

Last updated June 13, 2026

Every program you've written has lived in one file, main.rs, and every function and type has had a unique name because you chose them all yourself. That works until it doesn't. Real programs have hundreds of functions written by many people across many files, and sooner or later two of them want to be called connect, or parse, or new. This lesson is about the problem that creates, and the tool Rust gives you to solve it: modules.

The naming collision problem

Imagine you're writing a program that talks to both a database and a network service. Both naturally have a function called connect:

fn connect() {
    println!("connecting to the database");
}

fn connect() {
    println!("connecting to the network");
}

fn main() {
    connect();
}
error[E0428]: the name `connect` is defined multiple times
 --> src/main.rs:5:1
  |
1 | fn connect() {
  | ------------ previous definition of the value `connect` here
...
5 | fn connect() {
  | ^^^^^^^^^^^^ `connect` redefined here
  |
  = note: `connect` must be defined only once in the value namespace of this module

Two functions, one name, and the compiler refuses to choose. You could rename them connect_database and connect_network, and for two functions that's fine. But this approach falls apart at scale: prefixing every name with the area it belongs to is exactly the manual bookkeeping that programming tools are supposed to do for you. The error message even hints at the real answer with the phrase "namespace of this module." There's a better way than gluing prefixes onto names.

Modules: named scopes for code

A module is a named container for code. You group related items (functions, types, other modules) inside a mod block, and their names then live inside that module rather than out in the open:

mod database {
    fn connect() {
        println!("connecting to the database");
    }
}

mod network {
    fn connect() {
        println!("connecting to the network");
    }
}

Now there are two functions named connect, and no collision, because one is database::connect and the other is network::connect. The module name and the :: path separator (the same one from String::from and TrafficLight::Red) keep them distinct. Each module is its own little world; a name defined in one doesn't clash with the same name in another. This is the idea behind organizing any large codebase: carve the program into named regions so that names only have to be unique within a region.

Key insight

Modules solve the same problem at the code level that structs solved at the data level: organization. A struct groups related values under one name; a module groups related code under one name. Both let you stop juggling a flat pile of loose things and start working with meaningful, named groupings. As a program grows, this organization stops being a nicety and becomes the only thing keeping it comprehensible.

What this chapter replaces

If you've heard that C and C++ have a reputation for fiddly project setup, this chapter is a large part of why Rust doesn't. To split a program across files and manage names, C++ needs header files, header guards (or #pragma once), forward declarations, the preprocessor's #include text-pasting, and a separate namespace feature, spread across many lessons in a course like learncpp. Rust folds all of that into one coherent system: modules for organization and namespacing, and a single line to pull in another file (lesson 13.4). There are no headers, no include guards, and no preprocessor.

Author's note

This is the single biggest place where "the Rust course is shorter" stops being a slogan and becomes visible. A C++ curriculum spends a whole chapter on the mechanics of getting two files to see each other's functions without the linker complaining. We spend a few lessons, most of which are about design (how to organize code well) rather than ceremony (how to make the build system cooperate). The ceremony just isn't there.

The road map for this chapter

Here's where the next lessons go. Lesson 13.2 shows how to define modules and refer to things inside them with paths and use. Lesson 13.3 covers pub, the keyword that controls what's visible outside a module, which is how you build clean boundaries between parts of a program. Lesson 13.4 is the payoff: moving modules into separate files, the lesson that replaces that whole C++ chapter. After that, crates and Cargo (lessons 13.5 and 13.6), and finally Rust's documentation culture (lessons 13.7 and 13.8).

For now, the takeaway is just the motivation: as code grows, names collide, and modules are the named scopes that prevent it.

Quiz time

Question #1

Why does prefixing every function name (like connect_database, connect_network) not scale as a solution to naming collisions?

Show solution

It's manual bookkeeping that gets worse as the program grows: every name has to carry the region it belongs to, you have to remember and type those prefixes everywhere, and there's nothing stopping two people from choosing different prefixes for the same area. Modules do the grouping automatically, so names only need to be unique within their module, and the module name does the disambiguating.

Question #2

This is refused. Name the error and fix it with modules so both area functions can coexist.

fn area() { println!("circle"); }
fn area() { println!("square"); }
Show solution

E0428: the name area is defined multiple times. Put each in its own module:

mod circle {
    fn area() { println!("circle"); }
}

mod square {
    fn area() { println!("square"); }
}

Now they're circle::area and square::area, distinct names in distinct modules.

Question #3

In one sentence, what does a module do, and what familiar tool does it parallel?

Show solution

A module is a named container that groups related code so its names live inside that scope rather than out in the open. It parallels a struct: a struct groups related values under one name, a module groups related code under one name.

Modules organize code and prevent collisions. The next lesson gets concrete: defining modules, referring to their contents with paths like crate::, self::, and super::, and bringing names into scope with use.