Rust (2024 edition) Module 2 – Values, types, functions and control flow
Variables, mutability, shadowing and constants
Bind values with let and mut, reuse a name by shadowing, write a type where Rust cannot infer it, and choose between let, const and static.
What you will learn
- Declare immutable and mutable bindings, and fix error E0384 when a binding is assigned twice
- Use shadowing to turn a value into a new one, of a new type if needed, and tell it apart from mut
- Annotate a type where inference cannot decide, such as the result of parse
- Choose between let, const and static, and explain why Rust 2024 refuses references to a static mut
- Predict when a binding is dropped, and what a name starting with _ and a bare _ change
Before you start
What you’ll learn · Before you start
What you will learn
- Declare immutable and mutable bindings, and fix error E0384 when a binding is assigned twice
- Use shadowing to turn a value into a new one, of a new type if needed, and tell it apart from mut
- Annotate a type where inference cannot decide, such as the result of parse
- Choose between let, const and static, and explain why Rust 2024 refuses references to a static mut
- Predict when a binding is dropped, and what a name starting with _ and a bare _ change
Before you start
On this page
Add up three groups of passengers at a ticket gate with sold = sold + group;, and rustc refuses the eight-line
program: error E0384, cannot assign twice to immutable variable. In Rust a name keeps the first value it is given
unless you ask for it to change, and the compiler holds you to that.
This lesson covers each way a name and a value can be tied together: a binding: A name tied to a value by let or by a pattern. It is immutable unless it is declared with mut, and it lasts until the end of the block that made it. that
never changes, one that may (mut), a new binding under an old name, and values fixed for the whole program (const
and static). Each comes with the error or warning rustc 1.99.0 prints when it is misused, and the lesson ends with
the moment each value is drop: What happens to a value when the binding that owns it goes out of scope: its clean-up runs and any memory it owns is freed. A block's bindings are dropped in the reverse order they were made..
A let binding keeps its first value unless you write mut
Here is the gate’s counter: it starts at 0 and adds three groups of passengers, 2, 3 and 1. The compiler refuses it before any program exists:
// Counting the tickets sold to three groups at a gate.
fn main() {
let sold = 0;
for group in [2, 3, 1] {
sold = sold + group;
}
println!("tickets sold: {sold}");
} Output (exit status 1)
Printed as an error (standard error)
error[E0384]: cannot assign twice to immutable variable `sold`
--> sold.rs:5:9
|
3 | let sold = 0;
| ---- first assignment to `sold`
4 | for group in [2, 3, 1] {
5 | sold = sold + group;
| ^^^^^^^^^^^^^^^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
3 | let mut sold = 0;
| +++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0384`.
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O sold.rs && ./sold
The ^^^^^ line marks what rustc refuses, the assignment on line 5, and the ---- label says where sold got its
first and only value, on line 3. A name bound with let is immutable: it keeps that value until it goes away. The
help line names the fix, and with it the program builds and counts 6:
// The same count, with a binding that may change.
fn main() {
let mut sold = 0;
for group in [2, 3, 1] {
sold = sold + group;
}
println!("tickets sold: {sold}");
} Output
tickets sold: 6
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O sold_mut.rs && ./sold_mut
let mut makes the binding mutable: an assignment may give it a new value, as often as you like, but always a value
of the same type. Everything else stays as it was, so the word mut is the whole difference between the two
programs.
Immutable is the default because many values in a program never change once they are worked out, and a reader who
sees let without mut knows the name means one value until its block ends. The compiler checks that promise on
every line, so you never have to look for an assignment hidden further down. It checks the other direction too: a
mut that nothing ever uses gets a warning, which the session in the last concept section shows.
Adding mut everywhere to make errors go away
let mut on every binding compiles, but it throws away the promise that a value stays put, and rustc then warns
about every mut that changes nothing. Write mut only where the value really changes, such as the running total
in sold_mut.rs.
Checkpoint
Check: Immutable bindings and mut
Shadowing makes a new binding under the same name
A program that asks its user for a number meets a naming problem at once. The answer arrives as a line of text, its
spaces and newline have to go, and then it has to become a number: three values with one meaning. Rust lets all three
be called tickets. This program reads the line 4 from its standard input:
// One name for the line a user typed, the text in it and the number it means.
use std::io;
const TICKET_PAISE: u32 = 2_500;
fn main() {
let mut tickets = String::new();
io::stdin().read_line(&mut tickets).expect("no line to read");
println!("the line: {tickets:?}");
let tickets = tickets.trim();
println!("the text: {tickets:?}");
let tickets: u32 = tickets.parse().expect("not a whole number");
println!("the number: {tickets}, fare {} paise", tickets * TICKET_PAISE);
} Input (standard input)
4
Output
the line: " 4\n" the text: "4" the number: 4, fare 10000 paise
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O tickets.rs && ./tickets
Each let tickets makes a new binding that hides the one before it from that line on. That is
shadowing: Declaring a new binding with the name of an existing one. From that line on the name refers to the new binding, which may have another type, while the old value stays as it was until its block ends.. The new binding may have a new type: a String, then a &str, then a u32.
And the later bindings are immutable, because they were declared without mut, while the first one had to be mutable
for read_line to fill it. Step through the three bindings and watch what happens to the older values:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The first binding.
let mut tickets = String::new();makes an empty String, andread_lineappends the line the user typed: two spaces, a 4 and a newline. This firstticketsowns that text. - A second tickets.
let tickets = tickets.trim();is a new binding with the same name. Its value is a&strthat points at the 4 inside the same line: trimming copies nothing. - The old value stays. The first
ticketscan no longer be named, but it still owns the line, so the text the new binding points into stays valid. Shadowing hid a name; it dropped nothing. - A third tickets.
let tickets: u32 = tickets.parse()…makes a third binding, a number. The annotation: u32tellsparsewhich type to produce, and this binding is notmut, so it can never change. - The newest one answers. From here on,
ticketsmeans the number.tickets * TICKET_PAISEreads the u32 4 and gives 10000 paise, the fare the program prints. - All three are dropped. At the end of
mainthe three bindings are dropped, the newest first: the number, then the&str, then the String, which frees the line. Each binding lived until the end of the block that made it.
The older values are still there: the trimmed text points into the line, which the first tickets still owns.
Shadowing changes which value a name refers to; it never changes or drops a value. Try the same conversion with one
mutable binding instead, and the type gets in the way:
// mut lets a value change, but never its type.
fn main() {
let mut tickets = " 4";
tickets = tickets.trim();
tickets = tickets.parse::<u32>().expect("not a whole number");
println!("{tickets}");
} Output (exit status 1)
Printed as an error (standard error)
error[E0308]: mismatched types
--> retype.rs:5:15
|
3 | let mut tickets = " 4";
| ----- expected due to this value
4 | tickets = tickets.trim();
5 | tickets = tickets.parse::<u32>().expect("not a whole number");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `&str`, found `u32`
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0308`.
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O retype.rs && ./retype
tickets was a &str from its first line, so every value assigned to it must be a &str, and a u32 is refused with
E0308, mismatched types. Choose between the two by what the code does:
| Criterion | let mut | Shadowing: let again |
|---|---|---|
| What changes | The value inside the same binding | Which binding the name refers to |
| A new type | Refused (E0308) | Allowed |
| The old value | Replaced | Kept until its block ends, out of reach |
| Later changes | Allowed | Only if the new binding is mut |
| When to choose | A running total, a counter, a buffer you fill | Steps of a conversion: line, text, number |
Key idea
mut lets one binding take new values of the same type; shadowing makes a new binding, of any type, and leaves the
old value alone.
Checkpoint
Check: Shadowing a name
Rust works out each type, and asks when it cannot
Most bindings in this lesson have no type written, yet each has exactly one type, fixed when the program is compiled. rustc infers it from the value and from how the name is used afterwards. This program asks the standard library which types it chose:
// No types written, except one: what did rustc choose?
use std::any::type_name_of_val;
fn main() {
let tickets = 4;
let price = 25.0;
let fare: u64 = 2_500;
let total = fare * 4;
let typed = "7".parse::<u8>().expect("not a whole number");
println!("tickets: {}", type_name_of_val(&tickets));
println!("price: {}", type_name_of_val(&price));
println!("fare: {}", type_name_of_val(&fare));
println!("total: {}", type_name_of_val(&total));
println!("typed: {}", type_name_of_val(&typed));
} Output
tickets: i32 price: f64 fare: u64 total: u64 typed: u8
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O inferred.rs && ./inferred
tickets holds a whole number and nothing else in the program says which kind, so it gets the default for integers,
i32; price gets f64, the default for numbers with a decimal point. total is a u64 because fare is one, and
typed is a u8 because the call asked for it. (type_name_of_val is meant for messages like these; its exact text
may change between compiler versions.)
When nothing can decide, the program does not compile. parse can turn text into dozens of types, and in this
program nothing says which one is wanted:
// parse can produce many types, and nothing here says which one.
fn main() {
let text = "4";
let tickets = text.parse().expect("not a whole number");
println!("{tickets} tickets");
} Output (exit status 1)
Printed as an error (standard error)
error[E0284]: type annotations needed
--> untyped_parse.rs:4:9
|
4 | let tickets = text.parse().expect("not a whole number");
| ^^^^^^^ ----- type must be known at this point
|
= note: cannot satisfy `<_ as FromStr>::Err == _`
= note: the type must also implement `FromStr`
note: required by a bound in `core::str::<impl str>::parse`
--> …/lib/rustlib/src/rust/library/core/src/str/mod.rs:2782:21
|
2782 | pub fn parse<F: FromStr>(&self) -> Result<F, F::Err> {
| ^^^^^^^ required by this bound in `core::str::<impl str>::parse`
help: consider giving `tickets` an explicit type
|
4 | let tickets: /* Type */ = text.parse().expect("not a whole number");
| ++++++++++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0284`.
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O untyped_parse.rs && ./untyped_parse
Read the first line and the help: E0284, type annotations needed, and a suggestion to give tickets an explicit
type. The notes in between point into the source of the standard library, at the rule that parse needs a type it
can produce. Either of two places can name it: on the binding, let tickets: u32 = text.parse()…, as tickets.rs
does, or in the call, text.parse::<u32>(), the form Rust programmers call the turbofish.
Annotate the binding, not every binding
One annotation is usually enough: once tickets is a u32, every binding computed from it is inferred from it, as
total was from fare. Write a type where a reader would otherwise have to work it out.
Checkpoint
Check: Inferred types and annotations
const and static hold values fixed for the whole program
Some values belong to the program itself rather than to one run of a function: a ticket price, a limit, the name of a gate. Rust gives them items of their own, which may sit outside every function:
// Values fixed for the whole program: two constants and a static.
const TICKET_PAISE: u32 = 2_500;
const DAY_PASS_PAISE: u32 = TICKET_PAISE * 8;
static GATE: &str = "Gate 3";
fn main() {
const MAX_PER_SALE: u32 = 6; // a constant may live inside a function, too
println!("{GATE}: ticket {TICKET_PAISE} paise, day pass {DAY_PASS_PAISE} paise");
println!("at most {MAX_PER_SALE} tickets in one sale");
} Output
Gate 3: ticket 2500 paise, day pass 20000 paise at most 6 tickets in one sale
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O consts.rs && ./consts
A constant: A value declared with const and its type. The compiler works it out while it builds the program and copies it into every place that uses it, so a constant never changes. is computed while the program is built, TICKET_PAISE * 8 included, and copied
into every place that uses it, so it has no single address. A static is a single value at one place in memory
for the whole run: every use refers to that place, and it is never dropped. The Rust Reference advises a const
unless you need that single address, a large amount of data or a value that can change inside. Both need their type
written out, which the compiler enforces:
// A constant without its type.
const TICKET_PAISE = 2_500;
fn main() {
println!("ticket: {TICKET_PAISE} paise");
} Output (exit status 1)
Printed as an error (standard error)
error: missing type for `const` item --> const_type.rs:2:19 | 2 | const TICKET_PAISE = 2_500; | ^ help: provide a type for the constant: `: i32` error: aborting due to 1 previous error
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O const_type.rs && ./const_type
The convention is capitals with underscores for both, and a warning, non_upper_case_globals, says so when a name
breaks it. A static mut, a static that may change, is where the 2024 edition draws a hard line. Reading or writing
one already needed an unsafe block, and println! reads a value through a reference, which is now refused:
// A static that may change, read the way println! reads a value.
static mut SOLD: u32 = 0;
fn main() {
unsafe {
SOLD += 4;
println!("tickets sold: {SOLD}");
}
} Output (exit status 1)
Printed as an error (standard error)
error: creating a shared reference to mutable static
--> static_mut.rs:7:34
|
7 | println!("tickets sold: {SOLD}");
| ^^^^ shared reference to mutable static
|
= note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives
= help: use a type that relies on "interior mutability" instead; to read more on this, visit <https://doc.rust-lang.org/reference/interior-mutability.html>
= note: for more information, see <https://doc.rust-lang.org/edition-guide/rust-2024/static-mut-references.html>
= note: `#[deny(static_mut_refs)]` (part of `#[deny(rust_2024_compatibility)]`) on by default
error: aborting due to 1 previous error
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O static_mut.rs && ./static_mut
The lint static_mut_refs is a warning in older editions and an error in 2024, because the compiler cannot check
Rust’s rules for references on a value that any code may change at any moment. The help points to the way out, a
type with A type that lets its contents change even through a shared reference, because the type itself keeps the change safe: by copying values in and out, by checks while the program runs, or with atomic instructions. Cell, RefCell, Mutex and the atomic types are examples. such as an atomic counter, which the concurrency module covers. Until then, a value that changes
belongs in a let mut binding inside a function. Here are the three side by side:
| Property | let |
const |
static |
|---|---|---|---|
| Where it can be declared | in a block | anywhere | anywhere |
| Type written out | optional | required | required |
| Value worked out | when the line runs | when the program is built | when the program is built |
| In memory | while its block runs | copied to every use | one place for the whole run |
| May change | with mut |
never | with interior mutability, or as a static mut in unsafe |
Checkpoint
Check: const and static
A binding lives until the end of its block
Every { … } is a block, and a block is a scope: The part of a program where a name can be used. A let binding's scope runs from its let statement to the end of the block that contains it.: a binding made inside it can be used up to
its closing brace and no further. That is why this loop, meant to add up three marks, ends with a total of 0:
// Meant to add up three marks.
fn main() {
let total = 0;
for mark in [72, 85, 64] {
let total = total + mark;
println!("inside the loop: {total}");
}
println!("after the loop: {total}");
} Output
inside the loop: 72 inside the loop: 85 inside the loop: 64 after the loop: 0
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O loop_total.rs && ./loop_total
The body of a for loop is a block of its own, run once per mark. let total = total + mark; makes a new binding
there, which ends with each pass, so the outer total is never changed. No warning appears, because every name is
used. The fix is the one from the first section: let mut total = 0; before the loop and total += mark; inside it.
When a scope ends, each of its bindings is dropped: its value’s clean-up runs, and memory it owns, such as the text
of a String, is freed. Numbers have nothing to clean up, so to make drops visible this program uses a type that
prints a line when one of its values is dropped. You will write types like it in later modules; here it only shows
the order:
// A ticket that prints a line when it is dropped, so each drop can be seen.
struct Ticket(&'static str);
impl Drop for Ticket {
fn drop(&mut self) {
println!(" dropped {}", self.0);
}
}
fn main() {
let _first = Ticket("ticket 1");
let _ = Ticket("ticket 2");
println!("after the line with _");
{
let _inner = Ticket("ticket 3");
println!("end of the inner block");
}
let _last = Ticket("ticket 4");
println!("end of main");
} Output
dropped ticket 2 after the line with _ end of the inner block dropped ticket 3 end of main dropped ticket 4 dropped ticket 1
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O drops.rs && ./drops
Step through main and match each drop to the line of output it printed:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- A binding for ticket 1.
let _first = Ticket("ticket 1");binds the first ticket. A name that starts with an underscore is still a binding, so this ticket lives until the end ofmain. - No binding for ticket 2.
let _ = Ticket("ticket 2");makes a ticket and binds nothing:_is the wildcard pattern, which takes no value. Nothing keeps the ticket, so it is dropped at the end of that statement, anddropped ticket 2is the first line printed. - Inside the inner block. The inner block opens with
{, and_innerbinds ticket 3 inside it.end of the inner blockis printed while ticket 3 still exists. - The inner block ends. At the closing
}the inner block ends, and its bindings go with it:dropped ticket 3is printed whilemainis still running. - A binding for ticket 4.
let _last = Ticket("ticket 4");binds the last ticket in main's own block, after ticket 1, andend of mainis printed. - main ends. At the end of
mainits bindings are dropped in the reverse order they were made:dropped ticket 4, thendropped ticket 1. The last binding made is the first one dropped.
Three rules came out of that run. A block’s bindings are dropped when it ends, in the reverse order they were made:
ticket 4 before ticket 1. An inner block drops its own bindings at its closing brace, while the outer block goes on.
And let _ = … makes no binding at all: _ is the wildcard pattern: The pattern _, which matches any value without binding it. let _ = make(); keeps nothing, so the value made there is dropped at the end of that statement., which matches
a value without taking it, so a value made on that line is dropped at the end of that statement. A name that starts
with an underscore, such as _first, is an ordinary binding. Unused names get warnings, and this session shows them
for a small program:
// Three names: one never changed, one never used, one meant to be unused.
fn main() {
let mut fare = 2_500;
let change = 500;
let _receipt = "printed later";
println!("fare: {fare} paise");
} from session import run
# unused.rs built for debugging: rustc prints its warnings, then the program runs.
run("""
rustc --edition 2024 unused.rs
./unused
""") Output
$ rustc --edition 2024 unused.rs warning: variable does not need to be mutable --> unused.rs:3:9 | 3 | let mut fare = 2_500; | ----^^^^ | | | help: remove this `mut` | = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default warning: unused variable: `change` --> unused.rs:4:9 | 4 | let change = 500; | ^^^^^^ help: if this is intentional, prefix it with an underscore: `_change` | = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default warning: 2 warnings emitted $ ./unused fare: 2500 paise
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 unused_session.py
fare never changes, so its mut gets a warning; change is never used, and the help suggests _change.
_receipt gets no warning, because the underscore tells rustc the name is unused on purpose. Pick by what should
happen to the value: _name keeps it until the block ends, _ lets it go at once. That difference matters for values
whose drop does something, such as a lock that is released when its guard is dropped.
Key idea
A binding lives until the end of the block that made it, and a block’s bindings are dropped in reverse order; a
_name keeps its value until then, while a bare _ keeps nothing, so a value made on its line is dropped at once.
Checkpoint
Check: Scopes, drops and unused names
Practise: fix a ticket receipt
This program should print a receipt for three tickets, and it has six binding mistakes of the kinds this lesson
showed. Copy it into a file, build it with rustc --edition 2024 receipt_broken.rs, and fix one error at a time.
rustc reports the mistakes in rounds, because some checks run only after others pass, so build again after each fix:
// A receipt for three tickets, with six binding mistakes.
const TICKET_PAISE = 2_500;
static gate: &str = "Gate 3";
fn main() {
let count = " 3\n";
count = count.trim();
let count = count.parse().expect("not a whole number");
let total = TICKET_PAISE * count;
let discount = 0;
if count >= 3 {
let discount = 500;
}
total = total - discount;
println!("{gate}: {count} tickets");
println!("to pay: {total} paise");
} Output (exit status 1)
Printed as an error (standard error)
error: missing type for `const` item --> receipt_broken.rs:2:19 | 2 | const TICKET_PAISE = 2_500; | ^ help: provide a type for the constant: `: i32` error: aborting due to 1 previous error
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O receipt_broken.rs && ./receipt_broken
It is finished when it builds with no warnings and prints two lines, Gate 3: 3 tickets and to pay: 7000 paise.
One mistake builds without an error and only shows in the total, and the warning about an unused variable points at
it. When your receipt matches, compare it with one way to do it:
// The receipt with its six mistakes fixed.
const TICKET_PAISE: u32 = 2_500;
static GATE: &str = "Gate 3";
fn main() {
let count = " 3\n";
let count = count.trim();
let count: u32 = count.parse().expect("not a whole number");
let mut total = TICKET_PAISE * count;
let mut discount = 0;
if count >= 3 {
discount = 500;
}
total -= discount;
println!("{GATE}: {count} tickets");
println!("to pay: {total} paise");
} Output
Gate 3: 3 tickets to pay: 7000 paise
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O receipt.rs && ./receipt
The fixes, in the order rustc reported them: the constant got its type; parse got : u32; the trimmed text became a
new binding with let instead of an assignment; total became mut; the inner let discount became an assignment
to the outer, now mutable, discount; and gate became GATE. Last comes the self-check. It opens with whatever
you have answered least well so far, and the feedback on each choice names the slip behind it.
Practice that adapts to you
Practise this lesson
14 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 (fresher): what is the difference between shadowing a variable and making it mutable?
Both let one name refer to new values, but they do it differently. A mut binding is one binding whose value is
replaced in place by assignment, and every new value must have the binding’s type. Shadowing writes let again: it
makes a new binding that hides the old one from that line on, may have a different type, and is itself immutable
unless declared mut; the old value is untouched and lives until its block ends. So a counter in a loop needs mut,
because shadowing inside the loop body makes a binding that ends with each pass, while converting a line of input
into a number is a natural use of shadowing. A good answer adds that shadowing in an inner block ends at the block,
which is a common source of totals that never change.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A service needs three program-wide values: a limit of 500 requests a minute, known when it is built; a read-only table of 5,000 delivery zones; and a count of requests served so far, updated by every thread. How do you declare each in Rust 2024, and why?
-
Mid to senior
A helper
Timer::start(name)returns a value that logs the time elapsed when it is dropped. A teammate putslet _ = Timer::start("load");at the top of a slow function, and every log line says 0 ms. Why, how do you fix it, and why did rustc not stop it?
Key takeaways
- A
letbinding keeps its first value: assigning to it again is error E0384, andlet mutallows new values of the same type. - Shadowing with
letmakes a new binding under the same name, possibly of a new type, and leaves the old value alone until its block ends. - rustc infers every type from the value and its uses, defaulting to
i32andf64, and asks for an annotation, as E0284 does forparse, when nothing decides. constvalues are computed when the program is built and copied to each use; astatichas one address for the whole run, and references to astatic mutare an error in Rust 2024.- A binding lives until the end of its block and bindings are dropped in reverse order;
_namekeeps a value until then, while_keeps nothing, so a value made on its line is dropped at once.
Terms in this lesson
- binding
- A name tied to a value by let or by a pattern. It is immutable unless it is declared with mut, and it lasts until the end of the block that made it.
- constant
- A value declared with const and its type. The compiler works it out while it builds the program and copies it into every place that uses it, so a constant never changes.
- drop
- What happens to a value when the binding that owns it goes out of scope: its clean-up runs and any memory it owns is freed. A block's bindings are dropped in the reverse order they were made.
- scope
- The part of a program where a name can be used. A let binding's scope runs from its let statement to the end of the block that contains it.
- shadowing
- Declaring a new binding with the name of an existing one. From that line on the name refers to the new binding, which may have another type, while the old value stays as it was until its block ends.
- wildcard pattern
- The pattern _, which matches any value without binding it. let _ = make(); keeps nothing, so the value made there is dropped at the end of that statement.
References
- The Rust Programming Language: Variables and Mutability (The Rust Project)
- The Rust Reference: Statements (let statements) (The Rust Project)
- The Rust Reference: Constant items (The Rust Project)
- The Rust Reference: Static items (The Rust Project)
- The Rust Reference: Patterns (wildcard pattern) (The Rust Project)
- The Rust Reference: Destructors (The Rust Project)
- Rust error codes index: E0384 (The Rust Project)
- Rust error codes index: E0284 (The Rust Project)
- The Rust Standard Library: str::parse (The Rust Project)
- The Rust Standard Library: std::any::type_name_of_val (The Rust Project)
- The Rust Edition Guide: Disallow references to static mut (The Rust Project)
- The rustc book: Warn-by-default lints (The Rust Project)
Report a problem with this lesson
Kept only in this browser. Your Learn progress