Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Every way to organize code

Functions, modules, files, visibility, library and binary crates, workspaces and features. Where each boundary goes and what it buys you.

Pick the smallest boundary that solves the problem you actually have.

  • Code is repeated → a function. Repeated with different types → a generic function.
  • One file is too long, or unrelated things sit together → modules, then one file per module. pub only on what the rest of the crate needs.
  • You want to test or reuse the logic → split the package into a library crate and a thin main.rs.
  • Several packages change together (a server, a CLI and a shared core) → a workspace, with one Cargo.lock and one target/.
  • Some users need a piece and others don't (a TLS backend, a serde integration) → a Cargo feature, off by default.
  • Other people will use it → decide the public API on purpose: re-export what they need at the crate root and hide the rest.

Inside one crate

Section

Functions

The first boundary, and the one to reach for first.

Section

mod and files

A module inline, in a file, or in a folder, and how Rust finds each.

Section

Visibility

Private by default; pub, pub(crate) and pub(super) open it step by step.

Formal. Every item (function, type, constant, module, field, method) is private by default: it can be used in the module that defines it and in that module's descendants, and nowhere else. pub makes it visible wherever its parent module is visible; pub(crate) anywhere in the same crate; pub(super) in the parent module; pub(in path) inside the named ancestor. A path is usable only if every module along it is visible from the place it is written. Struct fields have their own visibility, so a pub struct can keep every field private. Using an item that is not visible is an error (E0603 for an item, E0616 for a field, E0624 for a method).

In plain English. Everything starts locked inside the room it was made in. Rooms inside that room can see in; nobody else can. pub unlocks the door to whoever can already reach the room.

Example. Inside mod shop { pub struct Item { price: u32 } }, code outside shop can name shop::Item but can't read item.price (E0616) or build Item { price: 3 } (E0451).

Why this rule exists. Privacy lets a module keep promises: if only its own code can touch a field, only its own code can break the rules that field must follow, and the rest of the program can change freely around it.

An item is visible in its own module and the modules inside it, and nowhere else unless marked pub.

Section

Paths and use

crate::, self::, super::, and bringing names into scope.

Crates and packages

The three words, and which one is the unit of compilation.

lib.rs holds the work, main.rs only calls it.

Section

Workspaces

Several packages, one lock file, one build directory.

Section

Features

Optional pieces a user switches on in Cargo.toml.

Formal. When several packages in one build depend on the same crate (at the same semver-compatible version), Cargo builds that crate once, with the union of every feature any of them requested. A feature therefore must only add functionality (new items, optional dependencies, extra impls); enabling it must never remove an item or change existing behavior in a way that breaks code written without it.

In plain English. If one part of your program switches a feature on, it is on for everyone who uses that crate. So a feature should only ever add things, like an optional extra, never take something away.

Example. If your app uses serde without derive and a dependency enables serde/derive, your app's serde has derive too.

Why this rule exists. Building one copy of each crate keeps builds small and types compatible across the dependency graph; that only works if turning a feature on can't break anyone.

A feature must only add. Cargo unifies them across the whole build.

Shaping what others see

pub use lets the public path differ from where the code lives.

Names, argument types, and what to leave out.

Which changes to that API break callers.

Organizing by what a type has and needs, rather than by inheritance.