---
title: 'Modules and Crates'
source: 'https://academia.sh/en/courses/rust-fundamentals/modules-and-crates'
course: 'Rust Fundamentals'
language: en
updated: '2026-09-15T04:58:45+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Shows that mod and use declarations are items and produce no value on their own'
    - 'Compares calling the same name from inside and outside a module, with and without pub'
    - 'Measures the difference between a module and a crate by how two source files merge into a single compilation unit'
---

# Modules and Crates

mod and use are both items: neither produces a value on its own, one draws a name boundary, the other shortens an existing path. The same name is called both from inside and outside the module, and pub versus non-pub cases are compared; two separate source files are compiled with a single rustc call and produce a single crate. The limiting measurement shows that visibility must be granted separately at every link of the path: even if an item is pub, it is unreachable from outside if the module wrapping it is not pub.

Those five data-modeling lessons closed having asked one question throughout: does
something written in the source produce a value? This question never once touched on
**where in the source** a type or a function appears — a struct declaration, a map
variable, a function, which boundary is it defined within and how is it seen from outside
that boundary? The Setup and the Package Manager lesson deferred this question once
already: it said that the crate root is not a single file in a real project, that the
compiler starts there and reaches other files through `mod` declarations, but it did not
show this. This lesson — the course's wrap-up — opens that deferred question: do `mod` and
`use` produce a value, or do they only draw a boundary; and is a module's boundary the same
thing as a crate's boundary?

## `mod` Draws a Boundary, It Produces No Value Itself

Rust's Design Goals lesson already showed a module in the form `mod measure { pub fn
unit() -> i32 { 3 } }`, and that lesson's measurement core counted the `mod` declaration as
an item — but did not dwell on it. This lesson stops exactly there: the line `mod name {
... }` produces nothing when it runs. Just like the `struct` declaration in the Structs
lesson, it introduces the compiler to a name and a boundary, it is not itself evaluated.
Every item inside it — a function, a constant, another module — only bears a value once it
is **used**.

```rust
// mod_inside_outside.rs — mod is an item, the same name is called from inside and outside the module
mod measurement {
    pub fn unit() -> i32 {
        3
    }

    fn private_factor() -> i32 {
        10
    }

    pub fn scaled() -> i32 {
        unit() * private_factor()
    }
}

fn main() {
    let from_outside = measurement::unit();
    println!("{:<36}: {}", "measurement::unit() (pub, outside)", from_outside);

    let from_inside = measurement::scaled();
    println!("{:<36}: {}", "measurement::scaled() (pub, outside)", from_inside);
}
```

```
measurement::unit() (pub, outside)  : 3
measurement::scaled() (pub, outside): 30
```

**DM20.** `measurement`'s declaration never ran on any line; both outputs seen come from
the lines where `unit` and `scaled` are **called**. The name `unit` is called here from two
separate places: from inside `main` as `measurement::unit()` — from outside the module, by
the full path — and from `scaled`'s body as `unit()` — from inside the module, with no
prefix needed. The same name, the same item, produces the same value (`3`) from two
different places; what changes is only which side of the module boundary the caller stands
on.

The `private_factor` function was declared without `pub`, and it could only be called from
`scaled`'s body — that is, from inside the `measurement` module itself. This shows that
visibility in Rust is **closed by default**: an item is invisible from outside the module it
is defined in unless a single keyword — `pub` — is added. `unit` and `scaled` were
reachable from `main` because they carry this keyword; `private_factor` was not reachable
because it does not. If `measurement::private_factor()` were written inside `main`, this
line would not compile. This rejection is not placed in a code block here — for the same
reason established in the Pattern Matching lesson, source that fails to compile does not
enter a block — but the reason can be described: the compiler sees that the call is trying
to cross a module boundary through an item that does not carry `pub`, and rejects the
source.

## A File Boundary Becomes a Module Boundary — a Crate Is a Single Compilation Unit

The Setup and the Package Manager lesson said that the crate root is always a single file
in this course, but that in a real project the compiler starts there and reaches other
files through `mod` declarations. It is shown here: the syntax `mod name;` — bodyless,
ending only in a semicolon — tells the compiler to read the file `name.rs` in the same
directory and treat that file's **entirety** as the body of the module named `name`.

```rust
// helper.rs — the entire file pulled in by a mod name is the module's body (SOURCE)
pub fn visible_outside() -> i32 {
    private_constant() + 5
}

fn private_constant() -> i32 {
    100
}
```

```rust
// mod_file_boundary.rs — mod helper; turns a file boundary into a module boundary
mod helper;

fn main() {
    let value = helper::visible_outside();
    println!("{:<34}: {}", "helper::visible_outside()", value);
}
```

```
helper::visible_outside()         : 105
```

**DM21.** There are two separate source files in this measurement — `mod_file_boundary.rs`
and `helper.rs` — but the compile command ran once and produced a single binary: two files,
one crate. The name of this single compilation unit is the **crate**: the whole formed by
starting from a single source root given to `rustc` and gathering every file reached
through `mod`, passed through a single compilation step. The `private_constant` function
inside `helper.rs` does not carry `pub`; the same rule from the previous section applies
here too — `private_constant` can only be called from inside the `helper` module itself,
that is, from `visible_outside`'s body.

A module and a crate can be confused here, because both draw a boundary, but the boundaries
they draw are on different planes. A module is a name and visibility boundary; it does not
care whether the source sits in a single file or in ten — the `measurement` module in the
previous section showed this inside a single file. A crate, on the other hand, is a
compilation and linking boundary: however many files, however many modules it is made of,
everything that passes through a single `rustc` call belongs to the same crate. A crate can
carry more than one module — `mod_file_boundary.rs`'s crate root and the `helper` module
were two parts of the same crate here — but a module is never split across more than one
crate.

## `use` Is an Item: the Shortcut Itself Produces No Value

Once the boundary `mod` draws is established, writing a name inside that boundary with its
full path (`measurement::unit()`) every single time is not mandatory. A `use` declaration is
a name that shortens a path, and just like `mod`, it is an item: the line itself produces no
value, it only introduces a short name the following lines can use.

```rust
// use_item.rs — use is an item, a value is born only once the shortcut name is used
mod measurement {
    pub fn unit() -> i32 {
        3
    }
}

use measurement::unit;

fn main() {
    let via_shortcut = unit();
    let via_full_path = measurement::unit();
    println!("{:<31}: {}", "unit() (via use shortcut)", via_shortcut);
    println!("{:<31}: {}", "measurement::unit() (full path)", via_full_path);
    println!("{:<31}: {}", "equal", via_shortcut == via_full_path);
}
```

```
unit() (via use shortcut)      : 3
measurement::unit() (full path): 3
equal                          : true
```

**DM22.** The line `use measurement::unit;` sits before `main`, and no output is directly
tied to it — just like `mod helper;` in the previous section, it is not itself run. Both
calls — `unit()` via the shortcut, and `measurement::unit()` via the full path — produce the
same value (`3`); the `equal` line confirms this directly. `use` introduces a shortcut, not
a new computation or a new behavior: there is no difference between the value coming through
the shortcut and the value coming through the full path, because both point to the same
item, `measurement::unit`. This belongs to the same family as the previous section's
finding — a declaration adds no new computation to the source, it only adds a name the
following lines can read; `mod` does this for a boundary, `use` does the same for a path
inside an existing boundary.

## Limiting Measurement: Visibility Must Be Granted Separately at Every Link of the Path

The two measurements so far showed visibility at a single level: an item either carried
`pub` out to the module or it did not. The real boundary is a little more fine-grained than
that: even if an item is `pub`, if the module wrapping it is not `pub`, that item cannot be
reached from outside.

```rust
// visibility_chain.rs — visibility must be granted separately at every link of the path
mod outer {
    pub mod inner {
        pub fn value() -> i32 {
            hidden() + 1
        }

        fn hidden() -> i32 {
            6
        }
    }
}

fn main() {
    let result = outer::inner::value();
    println!("{:<37}: {}", "outer::inner::value() (two pub links)", result);
}
```

```
outer::inner::value() (two pub links): 7
```

**DM23.** The call `outer::inner::value()` compiled and gave `7`, because both links in the
path — the `inner` module and the `value` function — carry `pub`; `outer` itself does not
carry `pub`, but it is already visible from outside (from this same file) because it is
defined at the crate root's own level. If `inner` had been written without `pub`, only as
`mod inner { ... }`, the call `outer::inner::value()` would not compile even though the
`value` function itself still carried `pub` — because the path would have to pass through a
module boundary that does not carry `pub`. This source, too, is not placed in a code block,
by the same not-compiling-source rule; but its result can be described: for the compiler, a
single item's visibility is not enough — the visibility of every step along the path must be
granted separately.

This is also the boundary of the course's own wrap-up. The three measurements here — the
single-level `pub`/private contrast, a file boundary turning into a module boundary, a
multi-link visibility chain — treated visibility as a binary switch (open or closed).
Rust's intermediate grades — visible only within the crate, visible only to the parent
module, and so on — were never measured in this lesson, because the measurement axis was
still the same question: does what is written in the source produce a value. The difference
between `pub` and non-`pub` written in the source produced a single binary answer; the finer
grades within that binary answer are a separate subject, outside this axis.

## Summary

- A `mod` declaration is an item: it produces no value itself, it only introduces a name
  and a boundary; the same name can be called without a prefix from inside the module and
  by full path from outside.
- Visibility is closed by default; a single `pub` keyword opens an item to the outside of
  its module.
- The syntax `mod name;` turns a file boundary into a module boundary; several source files
  can be compiled in a single `rustc` call and form a single crate.
- `use` is an item: it introduces a name that shortens a path, it produces no value itself;
  the same item called through the shortcut and through the full path gives the same value.
- Visibility must be granted separately at every link of the path: even if an item is
  `pub`, it is unreachable from outside if a module wrapping it is not `pub`.

## Course Wrap-Up

This course carried a single question through twelve lessons: does something written in
the source produce a value, or does it only declare a name? The first six lessons put this
question to the language's smallest units — expressions, bindings, primitive types, control
flow; the next five lessons carried the same question to the data-modeling tools — structs,
enums, pattern matching, collections, ordered and hash structures. This last lesson added,
without changing the question itself, the question of **where** in the source something
appears: which boundary is an item defined within, and how is it seen from outside that
boundary?

Each of the twelve lessons measured its own slice and wrote its own limiting measurement;
the table below gathers all twelve measurements in one place.

| Lesson | Writing measured | Class (expression · statement · item) | Limiting measurement |
|---|---|---|---|
| Rust's Design Goals | `if`/`match` picks a branch and gives a value | expression | Class is read not from the nature of the work but from the method's signature (`insert` expression, `push` statement) |
| Setup and the Package Manager | entry point, crate root, manifest | item | The manifest is not inside the source; only the output's name changes when just that file changes, with the source unchanged |
| Variables and Immutability | a `let` binding | statement | Immutability is the binding's own boundary; sharing the data another way is a separate question, the subject of the next course |
| Primitive Types | constructing a value from one of five primitive types | expression | `()` is not valueless, it is a single-valued type |
| Functions and Expression Orientation | the body's last line (tail position) | expression | A semicolon silently drops the value; the computation still happens |
| Control Flow | an `if` without `else` | expression | Its value is always `()`; the source fails to compile if its body tries to produce another type |
| Structs | a `struct` declaration | item | Position is the sole identity in a tuple struct; field order is free in a named struct |
| Enums | an `enum` declaration | item | Variant count is not state count; a data-carrying variant carries an uncountable number of states |
| Pattern Matching | a `match` expression | expression | A catch-all arm provides exhaustiveness but does not protect: it silently covers a variant added later without handling it with its own logic |
| Collections | `push` / `insert` | statement · expression | If `insert`'s return value is not bound to a variable, the old value it overwrites is silently lost |
| Ordered and Set Structures | set insertion (`insert` → `bool`) | expression | The ordering guarantee depends on a condition: the key type has to satisfy the ordering requirement |
| Modules and Crates | `mod` declaration / `use` | item | Visibility must be granted separately at every link of the path; an item is unreachable if the module wrapping it is not, even if the item itself is `pub` |

The three classes — expression, statement, item — never changed from the start of the
course to its end; what changed was only the breadth of the writing these three classes
were applied to: from a single line to a function body, from a data type's declaration to a
module's boundary. Ownership and borrowing were deferred again and again throughout this
course — a binding's immutability (the Variables and Immutability lesson), a receiver's
`&self` sign (the Structs lesson), a reference's double `&` sign (the Collections
lesson) — each time passed over by saying this was the subject of Ownership and the Memory
Model. That course
opens this deferred question: who owns a value, what does borrowing a reference mean, and
what happens when a value is separated from its owner?
