Functions and control flow are the first place where Rust feels different from C-family languages even though the syntax looks familiar. The reason is one idea: in Rust almost everything is an expression, meaning it produces a value. A block produces a value. An if produces a value. A loop can produce a value. match produces a value. Once that idea clicks, a lot of Rust code, including the "missing semicolon" rule that confuses most beginners, becomes obvious.
In this lesson you will write functions with parameters and return values, learn the difference between statements and expressions, use if as an expression, write loop, while and for loops (including labelled loops and loops that return values), iterate over ranges and collections, and meet match, Rust's pattern-matching construct. Interviewers at this level typically ask why a function with a trailing semicolon fails to compile, what if returns, how to break out of nested loops, and why match must cover every case.
Defining functions
A function is declared with fn, a name, a parameter list and an optional return type after ->. Rust uses snake_case for function and variable names (lowercase words joined by underscores).
fn main() {
greet("Priya");
let area = rectangle_area(4, 7);
println!("area = {area}");
println!("gst on 1000 = {}", add_gst(1000.0, 18.0));
}
fn greet(name: &str) {
println!("Hello, {name}!");
}
fn rectangle_area(width: u32, height: u32) -> u32 {
width * height
}
fn add_gst(amount: f64, rate_percent: f64) -> f64 {
amount + amount * rate_percent / 100.0
}
Output:
Hello, Priya!
area = 28
gst on 1000 = 1180
Things to notice:
maincallsgreetandrectangle_areaeven though they are defined below it. Rust does not care about the order of function definitions in a file, unlike C, where a function must be declared before use.- Every parameter has a name and a type:
width: u32. Types on parameters are mandatory. -> u32declares the return type. A function without->returns the unit type(), meaning "nothing useful".- The body of
rectangle_areais justwidth * height, with noreturnkeyword and no semicolon. The value of the last expression in the body is the function's return value. greettakes a&str, a borrowed string slice. You will learn exactly what&means in lesson 5; for now, read&stras "some text the function can look at".
Parameter types are not inferred
Inside a function body, Rust infers types freely. In a signature it never does. This does not compile:
fn square(x) -> i32 {
x * x
}
fn main() {
println!("{}", square(4));
}
error: expected one of `:`, `@`, or `|`, found `)`
--> param_err.rs:1:12
|
1 | fn square(x) -> i32 {
| ^ expected one of `:`, `@`, or `|`
|
help: if this is a parameter name, give it a type
|
1 | fn square(x: TypeName) -> i32 {
| ++++++++++
help: if this is a type, explicitly ignore the parameter name
|
1 | fn square(_: x) -> i32 {
| ++
This is a parse error, so it has no error code: the parser expected a : and a type after the parameter name. The rule is deliberate. A function signature is a contract that callers rely on, and requiring explicit types means a change inside one function can never silently change the types other code sees. It also gives much better error messages, because the compiler knows what you intended.
| Language | Parameter types | Return type |
|---|---|---|
| Python | Optional hints, not enforced | Optional hint |
| JavaScript | None | None |
| Java, C | Required | Required (void for none) |
| Rust | Required | Required if not () |
Statements and expressions
This distinction is the key to the rest of the lesson.
- An expression evaluates to a value.
5,x + 1,rectangle_area(4, 7), a block{ ... }and anif ... else ...are all expressions. - A statement performs an action and does not produce a value. There are two main kinds: a
letdeclaration, and an expression statement, which is any expression followed by a semicolon. The semicolon throws the expression's value away.
So the semicolon is not just a line terminator as in C or Java. It turns an expression into a statement and discards its value.
x * x expression: its value is x squared
x * x; statement: computes x squared, then discards it
let y = 5; statement: binds y, produces no value
Because let is a statement, you cannot write let a = (let b = 5); as you might chain assignments in C (a = b = 5). An assignment such as b = 5 is an expression, but its value is (), not 5.
Blocks are expressions
A block is a pair of braces containing zero or more statements, optionally followed by a final expression without a semicolon, called the tail expression. The value of the block is the value of its tail expression, or () if there is none.
fn main() {
let price = 250;
let discounted = {
let discount = price / 10;
price - discount
};
println!("discounted = {discounted}");
}
Output:
discounted = 225
The block computes discount as 25, then its tail expression price - discount gives 225, which becomes the value of discounted. The temporary variable discount exists only inside the block, which is a neat way to keep helper variables out of the surrounding scope.
A function body is just a block, which is why its tail expression is the return value.
Reading the compiler error: the stray semicolon
This is the single most common beginner error in Rust. This does not compile:
fn square(x: i32) -> i32 {
x * x;
}
fn main() {
println!("{}", square(4));
}
error[E0308]: mismatched types
--> semicolon_err.rs:1:22
|
1 | fn square(x: i32) -> i32 {
| ------ ^^^ expected `i32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
2 | x * x;
| - help: remove this semicolon to return this value
Read it carefully:
E0308 mismatched types: the function promised ani32but the body produces().- The label on
squareexplains why: the body "has no tail orreturnexpression". The semicolon turnedx * xinto a statement, so the block ends with nothing and its value is(). - The
help:line points at the exact character to delete.
Whenever you see "expected X, found ()" on a function, look for a semicolon after the last line.
Early return with return
return exits a function immediately with a value. Idiomatic Rust uses the tail expression for the normal result and return only for early exits:
fn safe_divide(a: i32, b: i32) -> i32 {
if b == 0 {
return 0;
}
a / b
}
fn main() {
println!("{} {}", safe_divide(10, 3), safe_divide(10, 0));
}
Output:
3 0
Writing return a / b; as the last line also works, but Clippy flags it as needless_return, and Rust programmers find it noisy.
Interview tip
If asked "why does Rust not need a return keyword at the end of a function?", answer: a function body is a block, a block is an expression whose value is its final expression, and the function returns that value. A trailing semicolon turns the final expression into a statement, so the block's value becomes ().
if expressions
if works as you would expect, with two differences from C and Java: the condition needs no parentheses (they are allowed, but the compiler warns that they are unnecessary), and the braces are always required, even for a single line.
fn main() {
let marks = 72;
if marks >= 90 {
println!("Grade A");
} else if marks >= 75 {
println!("Grade B");
} else if marks >= 60 {
println!("Grade C");
} else {
println!("Needs improvement");
}
let result = if marks >= 40 { "pass" } else { "fail" };
println!("result = {result}");
}
Output:
Grade C
result = pass
The second if is an expression. Its value is the value of whichever branch runs, so it can sit on the right of let. This replaces the ternary operator cond ? a : b from C, Java and JavaScript, which Rust does not have.
The condition must be a bool
Rust has no "truthy" values. This does not compile:
fn main() {
let pending = 3;
if pending {
println!("You have pending tasks");
}
}
error[E0308]: mismatched types
--> if_bool_err.rs:3:8
|
3 | if pending {
| ^^^^^^^ expected `bool`, found integer
In C, Python and JavaScript, if pending is true for any non-zero number. Rust requires an explicit comparison: if pending > 0. The same applies to strings, collections and Option values. You write if !name.is_empty() rather than if name.
Both branches must have the same type
Because an if expression has one value, both branches must produce the same type. This does not compile:
fn main() {
let marks = 72;
let result = if marks >= 40 { "pass" } else { 0 };
println!("{result}");
}
error[E0308]: `if` and `else` have incompatible types
--> if_types_err.rs:3:51
|
3 | let result = if marks >= 40 { "pass" } else { 0 };
| ------ ^ expected `&str`, found integer
| |
| expected because of this
The compiler took the type of the first branch (&str) as the expected type and rejected the integer in the second. Statically typed languages need this rule: result must have a single type known at compile time. An if without an else has type (), so you can only use it as a statement, or when the if branch also produces ().
Loops
Rust has three loop keywords: loop, while and for.
| Loop | Runs | Typical use |
|---|---|---|
loop | Forever, until break | Retry logic, event loops, "search until found", computing a value with break value |
while cond | While a bool condition holds | Iterating until a numeric condition changes |
for x in iterable | Once per item of a range or collection | The default choice for walking over anything |
loop and break with a value
loop repeats its body until a break. Because loop is an expression, break can carry a value out of it:
fn main() {
let mut attempts = 0;
let mut value: u64 = 1;
let first_big = loop {
attempts += 1;
value *= 3;
if value > 1000 {
break value;
}
};
println!("first power of 3 above 1000: {first_big} after {attempts} steps");
}
Output:
first power of 3 above 1000: 2187 after 7 steps
Trace it: value goes 3, 9, 27, 81, 243, 729, 2187. On the seventh step it exceeds 1000, so break value ends the loop and 2187 becomes the value of first_big. Without break value, you would need a mutable variable declared before the loop and assigned inside it, which is exactly the pattern the expression form avoids.
continue skips the rest of the current iteration and starts the next. Both break and continue work in all three loop kinds, but only loop can break with a value. A while or for loop might run zero times, so it would have no value to give.
Why the compiler likes loop
The compiler knows that code after an infinite loop without a break is unreachable. A function whose body is loop { ... } with no break can therefore have any return type: its type is !, the never type, which means "this expression never finishes". This is why server main loops and functions that always panic type-check cleanly.
Labelled loops
When loops are nested, break and continue apply to the innermost loop. To target an outer loop, give it a label, written as an apostrophe followed by a name, and name the label in the break:
fn main() {
let grid = [[3, 8, 1], [9, 4, 7], [2, 6, 5]];
let target = 4;
let mut found = None;
'rows: for (r, row) in grid.iter().enumerate() {
for (c, &cell) in row.iter().enumerate() {
if cell == target {
found = Some((r, c));
break 'rows;
}
}
}
println!("found {target} at {found:?}");
}
Output:
found 4 at Some((1, 1))
break 'rows ends both loops as soon as the target is found. Without a label you would need a "found" flag checked after the inner loop, as you would in Java without labels or in Python. (Java does have labelled breaks; Python does not.)
The example also previews tools you will use constantly: iter() to walk over an array by reference, enumerate() to get the index alongside each item, the &cell pattern to copy the number out of the reference, and Option (Some((r, c)) or None) to say "found or not found".
while loops
while checks a condition before each iteration. This computes how many steps the Collatz sequence takes to reach 1 from 27: if n is even, halve it; otherwise replace it with 3n + 1.
fn main() {
let mut n: u64 = 27;
let mut steps = 0;
while n != 1 {
n = if n.is_multiple_of(2) {
n / 2
} else {
3 * n + 1
};
steps += 1;
}
println!("27 reaches 1 in {steps} steps");
}
Output:
27 reaches 1 in 111 steps
Note how the if expression on the right of n = replaces a four-line if/else with assignments in each branch. is_multiple_of is the standard method for "divides evenly" on unsigned integers; Clippy suggests it over n % 2 == 0.
for loops and ranges
for is the loop you will use most. It walks over anything that can produce a sequence of items: a range, an array, a vector, the characters of a string, and many more.
fn main() {
for i in 1..4 {
print!("{i} ");
}
println!("| 1..4");
for i in 1..=4 {
print!("{i} ");
}
println!("| 1..=4");
for i in (1..=4).rev() {
print!("{i} ");
}
println!("| (1..=4).rev()");
for i in (0..10).step_by(3) {
print!("{i} ");
}
println!("| (0..10).step_by(3)");
let cities = ["Pune", "Chennai", "Kochi"];
for (i, city) in cities.iter().enumerate() {
println!("{i}: {city}");
}
}
Output:
1 2 3 | 1..4
1 2 3 4 | 1..=4
4 3 2 1 | (1..=4).rev()
0 3 6 9 | (0..10).step_by(3)
0: Pune
1: Chennai
2: Kochi
| Range syntax | Includes | Example values |
|---|---|---|
a..b | a up to but not including b (half-open) | 1..4 gives 1, 2, 3 |
a..=b | a up to and including b (inclusive) | 1..=4 gives 1, 2, 3, 4 |
(a..=b).rev() | Same values, reversed | 4, 3, 2, 1 |
(a..b).step_by(k) | Every k-th value | (0..10).step_by(3) gives 0, 3, 6, 9 |
print! is like println! but without the newline at the end.
Ranges are half-open by default, like Python's range(1, 4). Coming from C or Java, think for (int i = 1; i < 4; i++). There is no C-style three-part for in Rust; ranges and iterators replace it.
Idiom: loop over the items, not the indices
Write for city in &cities (or cities.iter()) rather than for i in 0..cities.len() followed by cities[i]. Iterating directly is clearer, cannot go out of bounds, and lets the compiler skip bounds checks. If you need the position too, use .iter().enumerate(). Clippy's needless_range_loop lint catches the index-only version.
Iterators in one paragraph
A for loop works with any type that implements the IntoIterator trait. Behind the scenes, for x in thing asks thing for an iterator, an object with a next method that returns the next item or None when finished, and calls next until it gets None. Ranges are iterators; arrays and vectors produce iterators. Methods such as rev, step_by, enumerate, map and filter wrap one iterator into another. Lesson 14 covers iterators in depth; for now, knowing that for is "call next until None" is enough.
Choosing a loop
| Situation | Use |
|---|---|
| Walk over every item of a collection | for item in &collection |
| Count from a to b | for i in a..b |
| Repeat until a condition changes | while condition |
| Repeat until something succeeds, and get a value out | loop with break value |
| Leave several nested loops at once | Labelled break 'label |
A first look at match
match compares a value against a series of patterns and runs the code for the first one that matches. Each pattern => expression pair is called an arm. It is like a switch statement, but far more powerful, and like if, it is an expression.
fn describe(n: i32) -> &'static str {
match n {
0 => "zero",
1..=9 => "a single digit",
10 | 100 | 1000 => "a power of ten",
x if x < 0 => "negative",
_ => "something else",
}
}
fn main() {
for n in [0, 7, 100, -5, 42] {
println!("{n} is {}", describe(n));
}
}
Output:
0 is zero
7 is a single digit
100 is a power of ten
-5 is negative
42 is something else
The patterns used here:
| Pattern | Matches |
|---|---|
0 | Exactly the value 0 |
1..=9 | Any value in the inclusive range |
10 | 100 | 1000 | Any of several values (the | means "or") |
x if x < 0 | Any value, bound to the name x, but only if the guard x < 0 is true |
_ | Anything; the wildcard, used as a catch-all |
Arms are tried from top to bottom, and the first match wins. Unlike C and Java switch, there is no fall-through from one arm to the next and no break needed.
match must be exhaustive
The compiler checks that the patterns cover every possible value. If any value could slip through, the program does not compile. This does not compile:
fn day_name(day: u8) -> &'static str {
match day {
1 => "Monday",
2 => "Tuesday",
3 => "Wednesday",
}
}
fn main() {
println!("{}", day_name(2));
}
error[E0004]: non-exhaustive patterns: `0_u8` and `4_u8..=u8::MAX` not covered
--> match_err.rs:2:11
|
2 | match day {
| ^^^ patterns `0_u8` and `4_u8..=u8::MAX` not covered
|
= note: the matched value is of type `u8`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern, a match arm with multiple or-patterns as shown, or multiple match arms
|
5 ~ 3 => "Wednesday",
6 ~ 0_u8 | 4_u8..=u8::MAX => todo!(),
|
The compiler worked out exactly which u8 values are missing (0, and 4 through 255) and suggested an arm that covers them. The fix is usually a _ arm with a sensible result. todo!() in the suggestion is a macro that panics with "not yet implemented"; it is a placeholder, not a real fix.
Exhaustiveness checking is one of Rust's most valuable features. When you later add a new variant to an enum (lesson 7), every match that does not handle it becomes a compile error, so the compiler finds every place you need to update.
Matching on tuples: FizzBuzz
match can match several values at once by matching on a tuple. Here is FizzBuzz, a classic screening problem, written so the logic reads like the specification:
fn fizzbuzz(n: u32) -> String {
match (n % 3, n % 5) {
(0, 0) => "FizzBuzz".to_string(),
(0, _) => "Fizz".to_string(),
(_, 0) => "Buzz".to_string(),
_ => n.to_string(),
}
}
fn main() {
for n in 1..=15 {
print!("{} ", fizzbuzz(n));
}
println!();
}
Output:
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz
The tuple (n % 3, n % 5) holds both remainders. (0, 0) means divisible by both; (0, _) means divisible by 3 whatever the second remainder is. The order of arms matters: (0, 0) must come before (0, _), or 15 would print Fizz. to_string() converts text or a number into an owned String; owned strings are the subject of lesson 4.
Worked example: prime numbers up to 30
Putting functions, while, early return and for together:
fn is_prime(n: u32) -> bool {
if n < 2 {
return false;
}
let mut d = 2;
while d * d <= n {
if n.is_multiple_of(d) {
return false;
}
d += 1;
}
true
}
fn main() {
let mut primes = Vec::new();
for n in 1..=30 {
if is_prime(n) {
primes.push(n);
}
}
println!("primes up to 30: {primes:?}");
}
Output:
primes up to 30: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
Step by step:
is_primehandles 0 and 1 with an earlyreturn false.- It tries divisors
dfrom 2 whiled * d <= n. Checking up to the square root is enough: ifn = a * bwith both factors greater than the square root, their product would exceedn. - If any
ddividesn, it returnsfalseimmediately. If the loop finishes, the tail expressiontrueis the result. mainuses aVec(a growable list, lesson 8) to collect the primes and prints it with{:?}.
For n = 29, the loop tries d = 2, 3, 4 and 5 (because 5 x 5 = 25 <= 29, but 6 x 6 = 36 > 29), finds no divisor, and returns true. For n = 21, it returns false at d = 3.
Idioms and pitfalls
Pitfall: a semicolon after the tail expression
fn f() -> i32 { 42; } fails with "expected i32, found ()". Remove the semicolon from the last line, or write return 42;. Likewise, let x = { 5; }; gives x the value ().
Pitfall: off-by-one with ranges
1..10 stops at 9. If you mean "1 to 10 inclusive", write 1..=10. When looping over indices, 0..v.len() is correct and 0..=v.len() goes one past the end and panics.
Pitfall: expecting truthiness
if count, while list and if name do not compile. Write the comparison you mean: count != 0, !list.is_empty(), !name.is_empty().
Idiom: prefer expressions to mutable placeholders
Instead of declaring let mut label = ""; and assigning it in each branch, write let label = if cond { "a" } else { "b" }; or let label = match x { ... };. The variable can then stay immutable, and the compiler checks that every branch provides a value.
Exercises
Exercise 1: maximum of three
Write fn max_of_three(a: i32, b: i32, c: i32) -> i32 using only if expressions (no std::cmp::max).
Solution
fn max_of_three(a: i32, b: i32, c: i32) -> i32 {
let bigger = if a > b { a } else { b };
if bigger > c { bigger } else { c }
}
fn main() {
println!("{}", max_of_three(4, 9, 2));
println!("{}", max_of_three(-3, -8, -1));
}
9
-1
Each if is an expression, so no mutable variable is needed. The -3, -8, -1 case checks that the function works with negatives.
Exercise 2: digit sum with while
Write fn digit_sum(n: u64) -> u64 that returns the sum of the decimal digits of n.
Solution
Take the last digit with % 10 and drop it with /= 10 until nothing is left. Marking the parameter mut n lets the function change its own copy.
fn digit_sum(mut n: u64) -> u64 {
let mut sum = 0;
while n > 0 {
sum += n % 10;
n /= 10;
}
sum
}
fn main() {
println!("{}", digit_sum(9875));
println!("{}", digit_sum(0));
}
29
0
9 + 8 + 7 + 5 = 29. For 0 the loop never runs and the result is 0.
Exercise 3: break with a value
Using loop and break value, find the smallest positive integer n such that n * n > 500.
Solution
fn main() {
let mut n = 1;
let answer = loop {
if n * n > 500 {
break n;
}
n += 1;
};
println!("smallest n with n*n > 500 is {answer}");
}
smallest n with n*n > 500 is 23
Check: 22 x 22 = 484, which is not more than 500, and 23 x 23 = 529, which is.
Exercise 4: grades with match
Write fn grade(score: u32) -> char returning 'A' for 90 to 100, 'B' for 75 to 89, 'C' for 60 to 74, 'F' for 0 to 59, and '?' for anything else.
Solution
Inclusive range patterns map directly onto the specification, and _ handles invalid scores, which also keeps the match exhaustive.
fn grade(score: u32) -> char {
match score {
90..=100 => 'A',
75..=89 => 'B',
60..=74 => 'C',
0..=59 => 'F',
_ => '?',
}
}
fn main() {
for s in [95, 75, 74, 12, 140] {
println!("{s} -> {}", grade(s));
}
}
95 -> A
75 -> B
74 -> C
12 -> F
140 -> ?
Exercise 5: leave two loops at once
Find the first pair (i, j) with 1 <= i < j <= 9 and i * j == 42, searching i from 1 upward, and stop both loops as soon as it is found.
Solution
A labelled break 'outer exits both loops.
fn main() {
let mut answer = (0, 0);
'outer: for i in 1..10 {
for j in (i + 1)..10 {
if i * j == 42 {
answer = (i, j);
break 'outer;
}
}
}
println!("{answer:?}");
}
(6, 7)
The factor pairs of 42 with both factors below 10 are only 6 x 7, so the answer is (6, 7).
Interview questions
Q1. What is the difference between a statement and an expression in Rust?
An expression evaluates to a value; a statement performs an action and has no value. let bindings and expressions followed by a semicolon are statements. Blocks, if, match and loop are expressions, which is why they can appear on the right-hand side of let or as a function's return value.
Q2. Why does adding a semicolon to the last line of a function cause a type error?
The function's return value is the value of its body block, which is the block's tail expression. A semicolon turns that expression into a statement, so the block has no tail expression and evaluates to (). If the signature promises, say, i32, the compiler reports "expected i32, found ()" and suggests removing the semicolon.
Q3. Does Rust have a ternary operator?
No, because if is already an expression. let x = if cond { a } else { b }; does the job, with the rule that both branches must have the same type. This also scales naturally to else if chains and multi-line branches.
Q4. Why must function parameters have type annotations when local variables do not?
A signature is a contract between a function and its callers. Requiring explicit types keeps type inference local to each function body, so a change in one function cannot silently change the types seen elsewhere, and error messages point at the right place. It also documents the function for readers.
Q5. How can a loop return a value?
loop is an expression, and break value ends it with that value, for example let found = loop { ... break x; };. Only loop supports this, because while and for may run zero times and would then have no value to return. All of them can use plain break and continue.
Q6. How do you break out of nested loops in Rust?
Label the outer loop with 'name: for ... (or while/loop) and write break 'name; in the inner loop. continue 'name; also works, moving to the next iteration of the labelled loop. This avoids boolean "done" flags.
Q7. What is the difference between 1..5 and 1..=5?
1..5 is half-open and yields 1 to 4; 1..=5 is inclusive and yields 1 to 5. Both are range types that implement Iterator, so they work with for, rev, step_by and other adapters. Ranges also appear in slicing (&v[1..3]) and as match patterns.
Q8. Why does Rust not have the C-style for loop?
Most C-style loops either count over a range or index into a collection. Rust covers both with iterators: for i in 0..n and for item in &collection. Iterating directly avoids off-by-one errors and lets the compiler remove bounds checks, while while and loop handle the rare cases that do not fit.
Q9. What does it mean that match is exhaustive?
The compiler checks that the arms of a match cover every possible value of the matched type, and refuses to compile otherwise (error E0004), listing the missing cases. The usual fix is a wildcard _ arm or explicit arms for the missing values. This makes match safe to rely on and turns "forgot to handle a case" from a runtime bug into a compile error.
Q10. How is match different from switch in C or Java?
match arms never fall through, so no break is needed. Patterns can be ranges, alternatives with |, tuples, nested data structures and bindings with guards, not just constants. match is an expression that produces a value, and it must be exhaustive. Java's newer switch expressions and pattern matching move in a similar direction, but C's switch does none of this.
Q11. What is the never type?
The never type, written !, is the type of expressions that never finish normally, such as an infinite loop without break, panic!(), return and continue. Because such an expression never produces a value, it can be used where any type is expected. That is why let x: i32 = if ok { 5 } else { panic!("bad") }; type-checks.
Key takeaways
- Functions use
fn name(param: Type) -> Return; parameter and return types are always written out, while local types are inferred. - Almost everything is an expression. A block's value is its tail expression, and a function returns its body's value.
- A semicolon turns an expression into a statement and discards its value, which is the cause of the classic "expected
i32, found()" error. ifis an expression, replaces the ternary operator, needs aboolcondition, and both branches must have the same type.loopcan return a value withbreak value; labels like'outerletbreakandcontinuetarget outer loops.forover ranges (a..b,a..=b) and collections is the default loop; iterate over items rather than indices.matchtries patterns top to bottom, never falls through, produces a value, and must be exhaustive.
Next lesson
Continue with Ownership in Rust.

