Rust (2024 edition) Module 1 – Getting started: rustup, Cargo and compiler messages
Why Rust: safety, speed and where it runs
Which bugs safe Rust refuses to compile, which it stops while the program runs and which it lets through, how releases work and where Rust runs.
What you will learn
- Explain which bugs safe Rust refuses to compile, which it stops at run time and which it lets through
- Identify a toolchain's release and edition, and when the next stable release arrives
- Describe where Rust is used today, citing the projects' own publications
- Read a recorded run: its command, its toolchain, what it printed and how it ended
What you’ll learn
What you will learn
- Explain which bugs safe Rust refuses to compile, which it stops at run time and which it lets through
- Identify a toolchain's release and edition, and when the next stable release arrives
- Describe where Rust is used today, citing the projects' own publications
- Read a recorded run: its command, its toolchain, what it printed and how it ended
On this page
Change two lines of the 14-line bill program on this page, and Rust refuses to build it: error E0382, a list used after it was handed to another function. Run as written, that program would use a list whose memory had already been freed, and the compiler stops it before a program exists.
That refusal is at the heart of what Rust offers, and it costs nothing while a program runs. This lesson shows what safe Rust rules out and what it still lets through, both on recorded runs, then how releases and edition: A set of language rules that each crate chooses in Cargo.toml: 2015, 2018, 2021 or 2024. A new edition changes nothing for crates that keep an older one, and crates of different editions work together. work and where Rust is used, each claim from the organisation that published it. By the end you can read every run in this track: the command, the toolchain, what it printed and how it ended.
Rust refuses to build a program that uses memory it gave away
Here is the program: a canteen bill of three items, kept in paise so that the arithmetic stays exact. total adds up
the amounts, and main prints each line, then the number of items and the sum:
// A canteen bill kept in paise, whole numbers, so no rounding error can creep in.
fn total(items: &[(&str, u64)]) -> u64 {
items.iter().map(|(_, paise)| paise).sum()
}
fn main() {
let items = vec![("dosa", 9_000), ("coffee", 3_500), ("vada", 4_250)];
for (name, paise) in &items {
println!("{name:<6} Rs {:>3}.{:02}", paise / 100, paise % 100);
}
let sum = total(&items);
let count = items.len();
println!("{count} items: Rs {}.{:02}", sum / 100, sum % 100);
} Output
dosa Rs 90.00 coffee Rs 35.00 vada Rs 42.50 3 items: Rs 167.50
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O bill_total.rs && ./bill_total
Now two lines change. total takes the list itself, Vec<(&str, u64)>, instead of a borrowed view of it, and the call
total(items) hands the list over. Line 12 still asks the list for its length:
// The same bill with two changes: total() takes the list itself, and the call hands it over.
fn total(items: Vec<(&str, u64)>) -> u64 {
items.iter().map(|(_, paise)| paise).sum()
}
fn main() {
let items = vec![("dosa", 9_000), ("coffee", 3_500), ("vada", 4_250)];
for (name, paise) in &items {
println!("{name:<6} Rs {:>3}.{:02}", paise / 100, paise % 100);
}
let sum = total(items);
let count = items.len();
println!("{count} items: Rs {}.{:02}", sum / 100, sum % 100);
} Output (exit status 1)
Printed as an error (standard error)
error[E0382]: borrow of moved value: `items`
--> bill_moved.rs:12:17
|
7 | let items = vec![("dosa", 9_000), ("coffee", 3_500), ("vada", 4_250)];
| ----- move occurs because `items` has type `Vec<(&str, u64)>`, which does not implement the `Copy` trait
...
11 | let sum = total(items);
| ----- value moved here
12 | let count = items.len();
| ^^^^^ value borrowed here after move
|
note: consider changing this parameter type in function `total` to borrow instead if owning the value isn't necessary
--> bill_moved.rs:2:17
|
2 | fn total(items: Vec<(&str, u64)>) -> u64 {
| ----- ^^^^^^^^^^^^^^^^ this parameter takes ownership of the value
| |
| in this function
help: consider cloning the value if the performance cost is acceptable
|
11 | let sum = total(items.clone());
| ++++++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0382`.
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O bill_moved.rs && ./bill_moved
No program was built. The compiler points at items.len() on line 12 with ^^^^^, and its other labels tell the
story: items was moved into total on line 11. Step through what the compiler follows to reach that error:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- An owner. let items = vec![…] puts three items on the heap. The name items in main is their owner: each value in Rust has exactly one owner at a time.
- Borrowed by the loop. for … in &items reads the list through a borrow, a reference that lends it out for a while. main still owns it when the loop ends.
- Handed over. total(items) moves the list into total: its parameter becomes the new owner, and items in main no longer owns anything.
- Freed at the end. When total returns, its parameter goes out of scope, so the list is dropped and its memory freed, as the third ownership rule says.
- A use after the move. Line 12 asks items for its length. Run as written, it would ask a list that total has already freed.
- rustc refuses. The compiler follows every owner before it builds anything. It reports error E0382, borrow of moved value, and stops: no program is built, so nothing runs.
- The fix lends the list. In bill_total.rs, total takes &[(&str, u64)] and the call is total(&items): the function borrows the list, main keeps it, and line 12 compiles.
The rules behind this are few. Each value has one The variable that a value belongs to. Each value in Rust has exactly one owner at a time, and the value is dropped, its memory freed, when the owner goes out of scope.
at a time, and when the owner goes out of scope the value is dropped and its memory freed. Passing a value to a
function Hands a value over to a new owner, such as a function's parameter. The old name can no longer be used, and the compiler refuses any use of it after the move.
it, unless the function only Uses a value through a reference (&) without taking it over. The owner keeps the value, and the compiler checks that the reference never outlives it.
it with &. The compiler checks these rules before it builds anything, so a program that breaks them is never built.
Cloning to make the error go away
rustc’s last suggestion, total(items.clone()), compiles, but it copies the whole list only to add it up. Its note is
the better fix here: let total borrow the list, &[(&str, u64)] and total(&items), as bill_total.rs does.
That is how Rust offers safety and speed together. The Rust book is explicit about the price of the checks: none of
them slow a program down while it runs. There is no garbage collector looking for unused memory while the program
runs, and none is needed, because the compiler has already worked out where each value is freed. And Rust is compiled
ahead of time to machine code: the bill_total that ran above is a program of its own, which runs on another computer
of the same kind without Rust installed.
Key idea
Rust follows who owns each value before it builds a program: code that would use memory after it was moved or freed is refused, and the checks cost nothing while the program runs.
Checkpoint
Check: What the compiler refuses
Safe Rust still lets some mistakes through
Refusing to build is one of three things safe Rust can do with a mistake. Some mistakes show only while the program runs, and then Rust stops it with a panic: Rust's way of stopping a program, or one thread of it, at an error it cannot carry on from, such as an index past the end of a list. It prints the reason and the line, and a program whose main thread panics exits with status 101. before anything goes wrong in memory; others it does not treat as unsafe at all. This program makes one of each kind:
// Two mistakes safe Rust lets through, and one it stops while the program runs.
fn main() {
// A u8 holds 0 to 255. Ten more bookings take 250 past the top.
let mut booked: u8 = 250;
for _ in 0..10 {
booked += 1;
}
println!("seats booked: {booked}");
// A logic error compiles: the 10% discount is taken twice.
let price = 200;
let discounted = price - price / 10;
let charged = discounted - discounted / 10;
println!("charged: {charged} (meant: {discounted})");
// Reading past the end never happens: get() says None, and [] stops the program.
let rows = vec![3, 5, 8];
println!("rows.get(7): {:?}", rows.get(7));
let wanted = rows.len() + 4;
println!("rows[{wanted}] = {}", rows[wanted]);
} The session builds it twice: first optimised, with -O, the way a release is built, then for debugging, the way rustc
builds without -O:
from session import run
# The program built twice: optimised, as a release is, and then for debugging, as rustc builds without -O.
run("""
rustc --edition 2024 -O limits.rs
!./limits
rustc --edition 2024 limits.rs -o limits-debug
!./limits-debug
""") Output
$ rustc --edition 2024 -O limits.rs $ ./limits seats booked: 4 charged: 162 (meant: 180) rows.get(7): None thread 'main' (…) panicked at limits.rs:20:41: index out of bounds: the len is 3 but the index is 7 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace [exit status 101] $ rustc --edition 2024 limits.rs -o limits-debug $ ./limits-debug thread 'main' (…) panicked at limits.rs:6:9: attempt to add with overflow note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace [exit status 101]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 limits_session.py
In the optimised build, 250 seats plus ten bookings printed 4: a u8 holds 0 to 255, and the count wrapped past the top
without a word. The discount was taken twice, so the program charged 162 instead of 180; the compiler cannot know
which formula you meant. rows.get(7) returned None, and rows[wanted] stopped the program before it read anything:
a panic with the file, the line and the reason, and exit status 101. The debug build stopped earlier, at the overflow
itself, because the Rust Reference requires a build with debug assertions to panic when arithmetic overflows.
| The mistake | What safe Rust does | Where you see it |
|---|---|---|
| A value used after it was moved or freed | Refuses to compile (E0382) | bill_moved.rs |
| Two threads changing the same data without a lock | Refuses to compile | the threads lessons, later |
| A read past the end of a list | Stops the program with a panic | rows[wanted] |
| A number past its type’s limit | Panics when built for debugging, wraps when optimised | booked |
| A wrong formula | Compiles and runs | charged |
| A deadlock or a memory leak | Allows it: not unsafe, though still a bug | the Rust Reference |
The last rows are easy to forget. The Reference lists deadlocks, memory leaks, integer overflow and logic errors as behaviour not considered unsafe, and the Rustonomicon says such problems are “considered impractical to categorically prevent”. What safe Rust promises is narrower and stronger: no undefined behaviour: What a program does once it breaks a rule the language relies on, such as reading memory that was freed: the language promises nothing about the result. Code written only in safe Rust never causes it., the class of bug after which the language promises nothing at all about how the program behaves.
Programmer Calculator (Bitwise) Add 250 and 10 at 8 bits, unsigned, and watch the result wrap to 4, as the optimised build did.Trusting tests to catch overflow
Tests are built for debugging, so an overflow that a test reaches panics there. A case no test reaches still wraps
silently in the optimised build that goes to users. Use checked_add, which returns None on overflow, wherever a
value can pass its limit.
Checkpoint
Check: What safe Rust lets through
A new stable Rust every six weeks, a new edition every few years
Rust’s compiler moves on a timetable. A new stable release ships every six weeks. Before it does, each release spends a
cycle on the beta channel, and the nightly channel is built every night from the newest code, with features that are
not finished yet. Editions are a separate thing: an edition is a set of language rules that
each A unit of Rust code that the compiler builds as a whole: a library or a program. A Cargo package holds one or more crates. chooses in its Cargo.toml, so far one every three years: 2015, 2018, 2021 and 2024. Walk through the
releases and editions this track depends on:
- 1.0
The first stable release
Rust 1.0 came out in 2015 and ended the years of breaking changes before it. Its language is what is now called the 2015 edition.
- 1.31
The 2018 edition edition
Rust 1.31 brought the first new edition, Rust 2018. A crate chooses its edition in Cargo.toml, and crates of different editions still work together.
- 1.56
The 2021 edition edition
Rust 1.56 stabilised Rust 2021, the third edition, on the same rule. An older crate keeps compiling with its own edition on every newer compiler.
- 1.85
The 2024 edition edition
Rust 1.85 stabilised Rust 2024, with changes to the language, the standard library, Cargo, rustdoc and rustfmt. cargo new writes edition = "2024" since then, and this track uses it.
- 1.90
Intel Macs move to Tier 2 platforms
The x86_64-apple-darwin target moved from Tier 1 to Tier 2 with host tools, still built but no longer fully tested. Linux on x86-64 switched to the LLD linker by default.
- 1.91
Windows on Arm joins Tier 1 platforms
aarch64-pc-windows-msvc became a Tier 1 target, with the full test suite run for every change, and a new warning caught functions that return a dangling raw pointer to a local variable.
- 1.99.0
The release this track uses this track
Six weeks after 1.98, Rust 1.99.0 lets C-style variadic functions be defined in Rust and stabilises APIs such as Vec::into_parts. Every example in this track ran on it.
- 1.99.1
A point release
Rust 1.99.1 fixed three regressions of earlier releases, among them a linker that could fail to start on macOS when the compiler ran without rustup's proxy.
- 1.100
Next on the train
The next release spends six weeks on the beta channel and becomes stable six weeks after 1.99.0. The project's promise is that code that works on one stable release keeps working when you upgrade to the next.
The timeline has 9 events, from 1.0 to 1.100.
So two numbers describe how your code is compiled. The release, such as 1.99.0, changes when you upgrade the toolchain,
and the project promises that code which works on one stable release keeps working on the next. The edition changes
only when you change it in Cargo.toml: a crate on the 2021 edition stays on it with every newer compiler, and the
rule the Edition Guide calls the most consequential is that crates of different editions work together. This track uses Rust 1.99.0 and
the 2024 edition, which cargo new writes into every new package; the next lesson installs and pins that release.
The official book and this track
The Rust book now assumes Rust 1.97.0 or later with edition = "2024", so its examples and this track’s agree, and
by the stability promise a newer stable compiler should build this track’s code too.
Checkpoint
Check: Releases and editions
Where Rust runs today, in the words of its users
Claims about Rust are easy to find and hard to check. These are the ones this track relies on, each from the organisation that published it:
| Where | What its publisher reports |
|---|---|
| Android | Memory-safety bugs fell below 20 % of Android’s vulnerabilities in 2025, and Rust code had a 1,000 times lower density of them than Android’s C and C++ (Google’s security blog) |
| The Linux kernel | Rust is an optional part of the kernel’s build, with Rust 1.85.0 as the minimum version (the kernel’s documentation) |
| Governments | The US agencies CISA and NSA published joint guidance on adopting memory-safe languages |
| Developers | The most admired language of the Stack Overflow 2025 survey (72 %), used by 14.8 % of all respondents and 23.1 % of those learning to code |
Read the Android row carefully: it says memory-safety bugs became a smaller share, not that the other bugs went away, which is exactly what the previous section predicts. The same report finds that, for medium and large changes, Rust changes are rolled back about four times less often than C++ changes.
The compiler builds for far more than desktops. Its platform support page lists Tier 1 targets, “guaranteed to work”,
for Linux, Windows and macOS on Apple silicon, and Tier 2 targets, “guaranteed to build”, that include WebAssembly
(wasm32-wasip1) and small Arm microcontrollers (thumbv7m-none-eabi, which has no std, only the core and alloc
parts of the standard library). Later modules of this track build for both.
The Rust project’s own survey is just as clear about the costs. Its survey for 2025, with 7,156 completed responses, again
found slow compile times and disk use among the biggest worries, along with concern that the language keeps growing
more complex. The rules that refused bill_moved.rs also take practice to learn, which is what the ownership module of
this track is for.
Checkpoint
Check: Where Rust is used, from the sources
Each run in this track shows its command, toolchain and output
No Rust compiler runs in this page, so every program in this track is shown with the output of a real run on Rust
1.99.0. A single program, such as bill_total.rs, shows the command that built and ran it,
rustc --edition 2024 -O bill_total.rs && ./bill_total, and everything it printed. A session, such as the limits run
above, shows several commands in a row: a line that starts with $ is a command, the lines under it are everything it
printed, errors included, and [exit status 101] says how it ended when that was not 0. The few things that change on
every run, such as build times and the number of the thread that panicked, are shown as …. Step through the parts
of the limits session, from the script above its output to the note under it:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The script. Above the output is the script that played the session: one command a line, run in the folder of the example. A ! before a command says it is meant to fail, and the transcript leaves the ! out.
- A command. In the output, a line that starts with $ is one command, exactly as typed. ./limits runs the program that the rustc line before it built.
- What it printed. The lines under a command are everything it printed, normal output and error messages together, in the order it wrote them.
- What changes. A part that changes on every run is shown as …, such as the number of the thread that panicked or the time a build took, so two runs show the same text.
- How it ended. A command that does not end with status 0 gets a line of its own. A Rust program that panics in main ends with 101; a command that succeeds gets no such line.
- The note under it. The note under the output names Python 3.14.8, which played the script. The commands themselves ran Rust 1.99.0, the release that the folder of the session pins.
To repeat a run yourself, install Rust 1.99.0 (the next lesson does it), save the file in a folder, open a terminal
there and type the commands that follow $. Your output should match, apart from the folder names and the … parts.
Checkpoint
Check: Reading a recorded run
Practise: what does safe Rust do with each mistake?
Each question describes one mistake. Decide whether safe Rust refuses to compile it, stops it with a panic while the program runs, or lets it through, then read the explanation, which names the rule that decides. Your answers set the order: ideas you get wrong come back sooner than the ones you get right.
Practice that adapts to you
Practise this lesson
12 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up: what does safe Rust guarantee, and what does it not?
Safe Rust guarantees memory safety and freedom from data races: code written only in safe Rust cannot use memory after
it is freed, free it twice, read past the end of a buffer or let two threads change the same data without
synchronisation. Most of these are refused at compile time, as E0382 refuses a use after a move; a read past the end of
a list is stopped at run time with a panic. It does not guarantee a correct program. Deadlocks, memory leaks, logic
errors and integer overflow are all possible in safe Rust: the Reference lists them as behaviour not considered unsafe,
and an optimised build wraps an overflowing integer silently unless overflow checks are switched on. A strong answer
adds that unsafe code inside a library must uphold the same rules, which is why its soundness matters.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
Your team wants to rewrite a C++ library that parses uploaded image files in Rust, because "Rust has no bugs". What would the rewrite remove, what would it not, and how would you decide whether it is worth doing?
-
Fresher to mid
A refund calculation subtracts two u64 amounts in paise. The tests pass, but in production a refund 100 paise larger than its charge produced 18,446,744,073,709,551,516 paise instead of an error. Why did the tests miss it, and what do you change?
Key takeaways
- Rust follows who owns each value before it builds a program, so code that uses memory after it was moved or freed is
refused, as
bill_moved.rswas with E0382, and the checks cost nothing while the program runs. - Safe Rust stops a read past the end of a list with a panic, but it lets logic errors, deadlocks, leaks and, in an optimised build, integer wrap-around through.
- A stable release ships every six weeks, while each crate chooses its edition in
Cargo.toml, and crates of different editions work together. - Android’s figures, the Linux kernel’s documentation and the Stack Overflow survey show where Rust is used, and the Rust project’s survey names its costs: compile times, disk use and a growing language.
- Every run in this track shows the command, the Rust 1.99.0 toolchain and exactly what was printed, with the exit status when it was not 0.
Terms in this lesson
- edition
- A set of language rules that each crate chooses in Cargo.toml: 2015, 2018, 2021 or 2024. A new edition changes nothing for crates that keep an older one, and crates of different editions work together.
- panic
- Rust's way of stopping a program, or one thread of it, at an error it cannot carry on from, such as an index past the end of a list. It prints the reason and the line, and a program whose main thread panics exits with status 101.
- undefined behaviour
- What a program does once it breaks a rule the language relies on, such as reading memory that was freed: the language promises nothing about the result. Code written only in safe Rust never causes it.
References
- The Rust Programming Language: What Is Ownership? (The Rust Project)
- The Rust Programming Language: Hello, World! (The Rust Project)
- The Rust Programming Language: How Rust is Made and Nightly Rust (The Rust Project)
- The Rust Reference: Behavior not considered unsafe (The Rust Project)
- The Rust Reference: Behavior considered undefined (The Rust Project)
- The Rustonomicon: Meet Safe and Unsafe (The Rust Project)
- The Rustonomicon: What Unsafe Rust Can Do (The Rust Project)
- The Rust Edition Guide: What are editions? (The Rust Project)
- The Rust Edition Guide: Creating a new project (The Rust Project)
- The Rust Edition Guide: Rust 2018 (The Rust Project)
- The Rust Edition Guide: Rust 2021 (The Rust Project)
- The Rust Edition Guide: Rust 2024 (The Rust Project)
- Announcing Rust 1.0 (The Rust Project)
- Announcing Rust 1.85.0 and Rust 2024 (The Rust Project)
- Announcing Rust 1.90.0 (The Rust Project)
- Announcing Rust 1.91.0 (The Rust Project)
- Announcing Rust 1.99.0 (The Rust Project)
- Announcing Rust 1.99.1 (The Rust Project)
- The rustc book: Platform Support (The Rust Project)
- The rustc book: thumbv7m-none-eabi (The Rust Project)
- 2025 State of Rust Survey Results (The Rust Project)
- Rust in Android: move fast and fix things (Google)
- Minimal requirements to compile the Kernel (The Linux Kernel documentation)
- Memory Safe Languages: Reducing Vulnerabilities in Modern Software Development (Cybersecurity and Infrastructure Security Agency (CISA))
- Stack Overflow 2025 Developer Survey: Technology (Stack Overflow)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress