Rust Fundamentals: Ownership, Errors and Fearless Concurrency · Rust Basics and Ownership
Types, Functions and Control Flow
Rust's number, boolean, char, tuple and array types, functions with typed parameters, expressions that return values, and if, loop, while, for and ranges.
What you'll build
A grade calculator: a function that turns a score into a letter, another that returns several values as a tuple, and loops that summarise a class's results.
The idea
Scalar types: integers (i32 default, u8, u64, usize for sizes and indexes…), floats (f64 default), bool, and char (a Unicode character in single quotes). Compound types: tuples (i32, f64, &str) and fixed-size arrays [i32; 5].
Functions declare parameter types and the return type after ->. The last expression without a semicolon is the return value:
fn double(x: i32) -> i32 {
x * 2 // no semicolon: this is the value returned
}
That's because Rust is expression-based: if, match and blocks produce values, so let label = if score >= 50 { "pass" } else { "fail" }; works.
Loops: loop (forever, until break, which can return a value), while condition, and for item in collection. Ranges: 1..5 (1 to 4), 1..=5 (1 to 5), .rev() to go backwards.
The code
fn letter(score: u32) -> char {
if score >= 90 {
'A'
} else if score >= 80 {
'B'
} else if score >= 70 {
'C'
} else {
'F'
}
}
fn stats(scores: &[u32]) -> (u32, u32, f64) {
let mut min = u32::MAX;
let mut max = 0;
let mut sum = 0;
for &s in scores {
min = min.min(s);
max = max.max(s);
sum += s;
}
(min, max, sum as f64 / scores.len() as f64) // a tuple: three values at once
}
fn main() {
let names = ["Ana", "Ben", "Cleo", "Dev"];
let scores = [92, 78, 85, 64];
for i in 0..names.len() {
let result = if scores[i] >= 70 { "pass" } else { "retake" }; // if is an expression
println!("{:<5} {:>3} {} {}", names[i], scores[i], letter(scores[i]), result);
}
let (min, max, avg) = stats(&scores); // destructure the tuple
println!("min {min}, max {max}, average {avg:.1}");
let mut attempts = 0;
let first_a = loop {
if scores[attempts] >= 90 {
break names[attempts]; // break can return a value from loop
}
attempts += 1;
};
println!("first A: {first_a}");
for n in (1..=3).rev() {
print!("{n}... ");
}
println!("done!");
}
Output
Ana 92 A pass Ben 78 C pass Cleo 85 B pass Dev 64 F retake min 64, max 92, average 79.8 first A: Ana 3... 2... 1... done!
Run this on your computer — see “Get Started” for setup.
Common mistakes
- Adding a semicolon to the return expression.
x * 2;becomes a statement, the function returns(), and the compiler reports "mismatched types". - Out-of-bounds indexing.
scores[10]compiles but panics at runtime; prefer iterating or.get(i). - Integer overflow surprises. In debug builds overflow panics; use wider types or
checked_addwhen values can grow. - Using
i32for lengths and indexes. Rust usesusizethere; convert withaswhen needed. 1..5vs1..=5. The first excludes 5.
Key points
- Integers, floats, bool, char; tuples and arrays for grouping.
- Functions declare types; the last expression (no semicolon) is returned.
if, blocks andloopare expressions that produce values.for x in collection, rangesa..banda..=b,.rev().
Further reading: Data types, Functions, Control flow.
Practice questions
Why does `fn double(x: i32) -> i32 { x * 2; }` fail to compile?
Answer
The semicolon turns the expression into a statement, so the block's value is the unit type (), which doesn't match the declared return type i32. Remove the semicolon (or use return x * 2;).
What does it mean that Rust is expression-based?
Answer
Most constructs produce values: if/else, match, blocks and loop (via break value) can be used on the right side of let or as a function's final expression.
Open in the app to mark lessons complete, take notes and track your progress.