Splitting code into files: mod and use
Two little keywords trip up many newcomers: mod and use. They look related, but they do completely different jobs. Once you see which is which, splitting a program into files is easy.
Remembermod says “this file is part of my program”, once, in the parent. use gives a short name to something, in any file that wants it.
This page is the step-by-step. The Modules lesson is the reference: every pub form, re-exports, globs and the prelude.
Two keywords, two jobs
Section titled “Two keywords, two jobs”mod pricing; |
use crate::pricing::total; |
|
|---|---|---|
| What it means | “There’s a module called pricing. Load it from pricing.rs.” |
“In this file, let me write total instead of crate::pricing::total.” |
| Where you write it | Once, in the parent file (usually main.rs) |
In any file that wants the short name |
| Loads a file? | Yes | Never |
| Needed for crates and std? | No | Optional: it’s only a shortcut |
| Closest thing in Go | Nothing: the compiler takes the .go files of a package folder |
import (of a whole package) |
| Closest thing in Python | import pricing does both jobs at once. Rust splits them in two. |
from pricing import total |
| Closest thing in TypeScript | Nothing as a statement: a file joins when something imports it | import { total } from "./pricing", which also loads the file |
| Closest thing in Java, Kotlin, C# | Nothing: the build gives the compiler its files | import (Java, Kotlin), using (C#) |
| Closest thing in C, C++ | Nothing in the code: the file is listed in the build | using in C++; C has none (#include pastes a header) |
Where names come from
Section titled “Where names come from”This explains why you see use lines with no mod lines in most files. Most names in your code come from one of three places, and only one of them needs mod:
| Where it comes from | Example | Needs mod? |
|---|---|---|
| The standard library | use std::collections::HashMap; |
No. std is available by name in every file. |
A crate in Cargo.toml |
use serde::Deserialize; |
No. Every normal dependency is available by name in every file (a hyphen in its name becomes _). |
| Your own files | use crate::models::Order; |
Yes, once, in the parent file. Then every file can use it. |
A small shop, in four files
Section titled “A small shop, in four files”Here’s a program split across four files. The notes on the right show which files have mod lines:
shop/ ├── src/ │ ├── pricing/ │ │ └── discounts.rs no mod lines │ ├── main.rs mod models; mod pricing; │ ├── models.rs no mod lines │ └── pricing.rs mod discounts; use crate::models::Order; ├── Cargo.lock └── Cargo.toml
mod models; // src/models.rs is part of this programmod pricing; // so is src/pricing.rs, plus anything it declares
use models::Order; // a shortcut: now we can write Order instead of models::Order
fn main() { let order = Order::new("ana", 3, 1250); println!("{}", order.summary()); println!("total after discounts: {}", pricing::total(&order));}// No `mod` lines here. main.rs already declared this file, once.
pub struct Order { pub customer: String, pub quantity: u32, pub unit_cents: u64,}
impl Order { pub fn new(customer: &str, quantity: u32, unit_cents: u64) -> Order { Order { customer: customer.to_string(), quantity, unit_cents, } }
pub fn summary(&self) -> String { format!( "{} x {} cents for {}", self.quantity, self.unit_cents, self.customer ) }}mod discounts; // src/pricing/discounts.rs: a module that lives inside pricing
use crate::models::Order; // no `mod models;` here: main.rs declared it, we only name it
pub fn total(order: &Order) -> String { let gross = order.unit_cents * order.quantity as u64; let net = gross - discounts::bulk(order.quantity, gross); format!("${}.{:02}", net / 100, net % 100)}// `discounts` isn't `pub` in pricing.rs, so only pricing can call this.pub fn bulk(quantity: u32, gross: u64) -> u64 { if quantity >= 3 { gross / 10 } else { 0 }}3 x 1250 cents for ana total after discounts: $33.75
Reading it file by file
Section titled “Reading it file by file”main.rsis where Rust starts. Its twomodlines say “models.rsandpricing.rsare part of this program”. Thenuse models::Order;lets it writeOrderinstead ofmodels::Order.models.rsdeclares nothing. It doesn’t need to:main.rsalready declared it.pricing.rshas onemodline, for its own childdiscounts. Because the parent ispricing, Rust looks for that file in a folder named after it:src/pricing/discounts.rs. It also usesOrder, with nomod models;, becausemain.rsalready declaredmodels.crate::means “start from the top of this crate”, and here the crate is the program (crates and packages).discounts.rsdeclares nothing. Its parent,pricing.rs, declared it.
The mod lines build a tree of modules, starting at main.rs. The use lines just point into it:
crate (src/main.rs)├── models (src/models.rs)└── pricing (src/pricing.rs) └── discounts (src/pricing/discounts.rs)To refer to something in this tree, start from the top with crate::, as in crate::models::Order, or one level up with super::, as in super::models::Order from inside pricing.
Questions people ask
Section titled “Questions people ask”Why do some files have use lines but no mod lines?
Section titled “Why do some files have use lines but no mod lines?”Because mod is written once, in the parent, and that’s usually main.rs (or lib.rs for a library). Every other file just uses what’s already been declared, plus things from std and your crates, which never need mod at all.
What if I write mod models; in pricing.rs too?
Section titled “What if I write mod models; in pricing.rs too?”Then you’re declaring a second, different module called models, inside pricing. Rust looks for its file at src/pricing/models.rs, doesn’t find it, and stops. The error’s last line says what to do instead:
error[E0583]: file not found for module `models` --> src/pricing.rs:2:1 | 2 | mod models; // a second, different module: pricing::models | ^^^^^^^^^^^ | = help: to create the module `models`, create file "src/pricing/models.rs" or "src/pricing/models/mod.rs" = note: if there is a `mod models` elsewhere in the crate already, import it with `use crate::...` instead
Does use make code slower, or load anything?
Section titled “Does use make code slower, or load anything?”No. use is resolved while compiling and loads nothing at run time. It only chooses how you spell a name: you can write the full path instead, like models::Order::new(...). Try it #2 below does that and prints the same lines.
What’s mod.rs?
Section titled “What’s mod.rs?”The older way to give a module a folder. src/pricing/mod.rs means the same as src/pricing.rs (Try it #3 moves the file to show it), and you’ll see both in real projects. The Book calls the pricing.rs style more idiomatic and the other “still supported”. Using both for one module is an error (layouts compared).
Sharing: items are private until you say pub
Section titled “Sharing: items are private until you say pub”- A function, struct, field or module is visible only inside its own module, and the modules inside that one, unless you mark it
pub(private by default). Enum variants and trait methods are the exception: they take the visibility of their enum or trait (fields and variants). - That’s why
Order’s fields arepub:pricing.rsreadsorder.quantity. discountsisn’tpubinpricing.rs, somain.rscan’t reach it, even thoughbulkinside it ispub. A module is only as visible as the path to it.pub(crate)means “visible anywhere in this crate, but not to other crates that use it as a library”. Try it #1 below shows thediscountsrule.
Try it yourself
Section titled “Try it yourself”-
Reach into
discountsfrommain.rs. Callpricing::discounts::bulk(...)there. The compiler says the module is private. Writepub mod discounts;inpricing.rsand it prints the discount, 375 cents:$ cargo builderror[E0603]: module `discounts` is private --> src/main.rs:11:45 | 11 | println!("discount alone: {}", pricing::discounts::bulk(order.quantity, gross)); | ^^^^^^^^^ ---- function `bulk` is not publicly re-exported | | | private module | note: the module `discounts` is defined here --> src/pricing.rs:1:1 | 1 | mod discounts; // src/pricing/discounts.rs: a module that lives inside pricing | ^^^^^^^^^^^^^^
terminal$ cargo run 3 x 1250 cents for ana total after discounts: $33.75 discount alone: 375 -
Delete the
useline. Removeuse models::Order;and writemodels::Order::new(...). The output is the same two lines:terminal$ cargo run 3 x 1250 cents for ana total after discounts: $33.75 -
Move
pricing.rsinto its folder. Renamesrc/pricing.rstosrc/pricing/mod.rs, nothing else. Same two lines again:terminal$ mv src/pricing.rs src/pricing/mod.rs $ cargo run 3 x 1250 cents for ana total after discounts: $33.75
How other languages decide what’s in your program
Section titled “How other languages decide what’s in your program”| Language | A file joins your program when… | Import | Shared by default? |
|---|---|---|---|
| Rust | its parent declares it: mod db; |
use crate::db::User; |
No: add pub |
| Go | it sits in the package folder | import "example.com/app/db" |
Other packages see only capitalised names |
| Python | something imports it | from app.db import User |
Yes; _name is private by convention only |
| TypeScript | something imports it (or tsconfig.json includes it) |
import { User } from "./db" |
No: add export |
| Java | the build compiles it (a source folder, or the files javac is given) |
import com.app.db.User; |
Package only: add public |
| Kotlin | the build compiles it (a source folder, or the files kotlinc is given) |
import app.db.User |
Yes (public is the default); internal and private exist |
| C# | it is in the project | using App.Db; |
Types: project only (internal); members: private. Add public |
| C | it is listed in the build | #include "db.h" |
Yes (external linkage); static hides |
| C++ | it is listed in the build | #include "db.h" or import db; |
Yes; static or an unnamed namespace hides |
Most rows above come from tiny projects run in the image. Open the log to see them:
The runs behind the table
$ ls # Go: helper.go is never mentioned by main.go db go.mod helper.go main.go $ go run . # Go helper.go is in the package, nothing declared it db $ go build ./... # Go: main.go now calls db.secret() # example.com/app ./main.go:11:17: name secret not exported by package db $ python3 main.py # Python: unused.py is beside it, never imported db.py loaded user still importable $ tsc bad.ts # TypeScript: db.ts has a Hidden without export bad.ts(1,10): error TS2459: Module '"./db"' declares 'Hidden' locally, but it is not exported. $ javac ... && java app.Main # Java user $ javac app/Bad.java app/db/Hidden.java # Java: Hidden has no public app/Bad.java:2: error: Hidden is not public in app.db; cannot be accessed from outside package import app.db.Hidden; ^ 1 error $ kotlinc Main.kt Helper.kt Priv.kt ... && java -jar # Kotlin Helper.kt joined without a declaration 2 $ kotlinc Bad.kt Priv.kt # Kotlin: secret() is private Bad.kt:2:18: error: cannot access 'fun secret(): Int': it is private in file. import app.other.secret ^^^^^^ Bad.kt:3:13: error: cannot access 'fun secret(): Int': it is private in file. fun bad() = secret() ^^^^^^ $ dotnet run # C#: Extra.cs is in the project, never mentioned Extra.cs joined without a declaration $ dotnet run # C#: Holder.secret has no modifier, so it is private /home/you/shop/cs/App2/Program.cs(1,32): error CS0122: 'Holder.secret' is inaccessible due to its protection level [/home/you/shop/cs/App2/Program.csproj] The build failed. Fix the build errors and run again. $ gcc main.c # C: util.c is not in the build /usr/bin/ld: /tmp/ccXXXX.o: in function `main': main.c:(.text+0x8): undefined reference to `util' collect2: error: ld returned 1 exit status $ gcc main.c util.c && ./a.out # C: util.c listed 42 $ gcc static_main.c static_hid.c # C: hid() is static /usr/bin/ld: /tmp/ccXXXX.o: in function `main': static_main.c:(.text+0x8): undefined reference to `hid' collect2: error: ld returned 1 exit status $ g++ main.cpp # C++: util.cpp is not in the build /usr/bin/ld: /tmp/ccXXXX.o: in function `main': main.cpp:(.text+0x8): undefined reference to `util()' collect2: error: ld returned 1 exit status $ g++ main.cpp util.cpp && ./a.out # C++ 42
Commands are shortened in the log (for example, tsc was run with --noEmit --target es2022 --module nodenext, and the javac and kotlinc lines are abbreviated). Java, Kotlin, C and C++ were given their file lists by hand, as a build tool would. The linker’s temporary file name is replaced by ccXXXX, because it is different on every run. C# ran from the image’s offline project template with dotnet run --no-restore: it shows that an unlisted Extra.cs is compiled, and that a member with no modifier is private. That a type with no modifier is internal was checked in a separate two-project run that is not in this log. Not run here: C++ import db;, C++ unnamed namespaces, and Python’s _name convention.
Creating a file and expecting it to just be there
Looks right at a glance, but Rust won't compile it.
No direct equivalent in Go. Just a Rust rule.No direct equivalent in Kotlin. Just a Rust rule.No direct equivalent in Python. Just a Rust rule.No direct equivalent in TypeScript. Just a Rust rule.Not run in Java on this page.Not run in C# on this page.C refuses this too. Same idea, different words.C++ refuses this too. Same idea, different words.// src/main.rs, without `mod pricing;`mod models;
use models::Order;
fn main() { let order = Order::new("ana", 3, 1250); println!("{}", order.summary()); println!("total after discounts: {}", pricing::total(&order));}In Go, the .go files in the package folder are included automatically (not _test.go files, files whose names start with _ or ., or files excluded by build constraints). There's nothing to declare.
In Kotlin the build gives the compiler a set of files, and files in the same package see each other without any declaration.
A Python file runs when something imports it, and not otherwise. Nothing declares it ahead of time, but nothing loads it by being in the folder either.
A TypeScript file is compiled when something imports it (or when tsconfig.json includes it). The import is the declaration.
There is no Java version of this one.
There is no C# version of this one.
#include <stdio.h>
int util(void);
int main(void) { printf("%d\n", util()); return 0;}/usr/bin/ld: /tmp/ccXXXX.o: in function `main': main.c:(.text+0x8): undefined reference to `util' collect2: error: ld returned 1 exit status
#include <iostream>
int util();
int main() { std::cout << util() << "\n"; return 0;}/usr/bin/ld: /tmp/ccXXXX.o: in function `main': main.cpp:(.text+0x8): undefined reference to `util()' collect2: error: ld returned 1 exit status
What does the compiler say? Guess first, then open.
error[E0433]: cannot find module or crate `pricing` in this scope --> src/main.rs:9:43 | 9 | println!("total after discounts: {}", pricing::total(&order)); | ^^^^^^^ use of unresolved module or unlinked crate `pricing` | help: to make use of source file src/pricing.rs, use `mod pricing` in this file to declare the module | 2 + mod pricing;
In Go, the .go files in the package folder are included automatically (not _test.go files, files whose names start with _ or ., or files excluded by build constraints). There's nothing to declare.
In Kotlin the build gives the compiler a set of files, and files in the same package see each other without any declaration.
A Python file runs when something imports it, and not otherwise. Nothing declares it ahead of time, but nothing loads it by being in the folder either.
A TypeScript file is compiled when something imports it (or when tsconfig.json includes it). The import is the declaration.
In Go, the .go files in a package folder are compiled together (test files and files excluded by build constraints aside). Build tools for Java, Kotlin and C# typically collect a whole source folder. In those languages, create the file and it's part of the build.
In Go, the .go files in a package folder are compiled together (test files and files excluded by build constraints aside). Build tools for Java, Kotlin and C# typically collect a whole source folder. In those languages, create the file and it's part of the build.
A C file that is not listed in the build is not compiled, and the linker reports an undefined reference to what is in it. Like mod, the build has to be told about the file.
Same as C: a .cpp file that is not listed in the build is not compiled, and the linker complains about what is missing. Like mod, the build has to be told about the file.
Rust starts at main.rs and only follows the mod lines it finds. src/pricing.rs exists, but nothing declared it, so it isn't part of the program.
Being explicit means the module tree is written in your code, not decided by whatever files happen to be in a folder. The fix is one line, and the compiler's help: shows exactly which one.
The fix
Compiles and runs.
mod models; // src/models.rs is part of this programmod pricing; // so is src/pricing.rs, plus anything it declares
use models::Order; // a shortcut: now we can write Order instead of models::Order
fn main() { let order = Order::new("ana", 3, 1250); println!("{}", order.summary()); println!("total after discounts: {}", pricing::total(&order));}3 x 1250 cents for ana total after discounts: $33.75
Read more
Section titled “Read more”- The Rust Book, chapter 7: Packages, Crates, and Modules, especially Control Scope and Privacy with Modules, Bringing Paths Into Scope with the use Keyword and Separating Modules into Different Files
- Rust by Example: File hierarchy and Visibility
- The Reference: Modules and Visibility and privacy
- This site: Modules (the reference lesson) and More than one program in a project for workspaces
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.