Course outline

Rust / Basic / Lesson 2

Types, expressions, and control flow

Represent values deliberately, choose arithmetic policies, and turn boundary requirements into branches and terminating loops.

About 4 min readContent reviewed
On this page

Lesson goals

  • Use immutable bindings, mutability, scalar types, and tuples.
  • Explain expression-valued branches and loop boundaries.
  • Use checked arithmetic instead of build-dependent overflow assumptions.

A binding names a typed value

let introduces an immutable binding by default; mut allows assignment or mutation through that binding when the type supports it. Shadowing creates a new binding and can change the type, unlike assigning to an existing variable. Integer types specify signedness and width, while usize is sized for the target address space and commonly represents collection indices. bool has true and false values, and char represents a Unicode scalar value. Choose types from the accepted domain and external format rather than assuming every quantity should be a pointer-sized integer.

Choose failure behavior before arithmetic

Integer division discards a fractional part, so assigning the quotient to a floating type later does not recover it. Convert operands before division if a fractional calculation is intended, and validate a nonzero denominator. Ordinary integer overflow can panic with overflow checks enabled and can wrap when checks are disabled; do not define application behavior by build profile. checked_add returns Option, letting the caller handle failure consistently. wrapping_add, saturating_add, and overflowing_add express different deliberate policies. Floating-point calculations introduce separate precision and rounding questions that integer overflow checks do not solve.

rust A quotient, remainder, and checked failure
fn main() {
    let pages: u32 = 11;
    let readers: u32 = 4;
    println!("{} {}", pages / readers, pages % readers);
    println!("{}", u8::MAX.checked_add(1).is_none());
}
Expected output
2 3
true

Eleven splits into two each with three remaining. Adding one beyond the maximum u8 returns None, not a profile-dependent wrapped value.

Branches partition the accepted domain

if requires a Boolean condition rather than treating arbitrary integers as truth values. An if expression can produce a value when its branches have compatible types. Check invalid input before ordinary classification so an out-of-range score cannot pass a threshold accidentally. Built-in && and || short-circuit, allowing a guard to precede a dependent operation. Keep side effects out of complicated conditions when that improves review. A match can later make finite alternatives exhaustive, but an if chain remains useful for ordered numeric regions with clearly assigned equality boundaries.

Explain a loop with an invariant

A for loop over 1..=4 visits one through four, including both endpoints. The range 1..4 excludes four. Before each iteration in the second program, total equals the sum of values already visited; adding next preserves that invariant. When the finite range ends, the result is ten. while suits repetition controlled by a changing condition, and loop can continue until break, including a break carrying a value. Always identify progress and termination before using a loop with external input, especially when invalid input can leave state unchanged.

rust A finite sum and an expression-valued decision
fn main() {
    let mut total = 0u32;
    for next in 1..=4 { total += next; }
    let label = if total >= 10 { "ready" } else { "wait" };
    println!("{total} {label}");
}
Expected output
10 ready

Four additions produce ten. Both branches return string slices, and equality selects ready.

Group values without hiding requirements

Tuples group a fixed set of possibly different types, and arrays contain a fixed number of one element type. Destructuring can give tuple fields meaningful local names. Rust requires initialization before a value is read, but initialization does not prove the value belongs to the application domain. Conversions using as can truncate or change numeric meaning; prefer TryFrom when rejection is required. Test zero, extrema, exact thresholds, and neighboring values. A compiler-accepted expression can still implement the wrong unit, rounding rule, or acceptance boundary, so keep domain tests explicit.

Common errors and fixes

A branch condition uses an integer directly.

Why it happens
Rust expects bool and does not imply numeric truthiness.
How to fix it
Write an explicit comparison such as count != 0.
Prevent it next time
Express the domain condition in words before coding.

Overflow behavior changes between builds.

Why it happens
Ordinary arithmetic was used as an implicit overflow policy.
How to fix it
Use checked or another explicitly chosen arithmetic operation.
Prevent it next time
Test numeric limits independently of optimization settings.

The final item is skipped.

Why it happens
An exclusive range was used where inclusion was needed.
How to fix it
Choose ..= or revise the bound deliberately.
Prevent it next time
Trace first, last, and empty iteration cases.

Check your understanding

1. Share seventeen tasks among five workers. Give the integer quotient and remainder, then describe the fractional calculation without changing integer division afterward.

Show worked solution

The quotient is three and the remainder two. For a fractional average, convert the numerator or both operands to f64 before division, giving 3.4 subject to floating representation. Converting the integer quotient would give 3.0 instead. Validate the divisor before generalizing the calculation to user-provided worker counts.

2. Predict the second example with 1..4 instead of 1..=4. Identify the invariant and explain the changed label.

Show worked solution

Only one, two, and three are visited, so total is six and the line is 6 wait. The invariant still says total equals the sum of already visited elements; only the range contract changed. This distinguishes a correct loop over the wrong range from a broken accumulation step. Add an empty range test to check the initial zero case.

Key takeaways

  • Types and arithmetic policies express separate decisions.
  • Assign equality and range endpoints deliberately.
  • Use invariants and boundary tests to explain behavior.

Official references