Skip to content
Learn Rust
Theme

If / Else

Basics · Lesson 10

if needs no parentheses around the condition. It is also an expression, so it can produce a value.

Coming from Goif looks like Go's: no parentheses, braces required, and the condition must be a bool. It is also an expression, so let size = if n > 5 { "big" } else { "small" }; replaces declaring size first and assigning in a branch.

Coming from KotlinThis is Kotlin's if expression without the parentheses, and the braces are required. Both branches must give the same type, where Kotlin would quietly pick a common supertype.

Coming from Pythonif with a value replaces "big" if n > 5 else "small". The condition must be a real bool: there is no truthiness, so if n with a number or if items with a list won't compile.

Coming from TypeScriptif with a value replaces the ternary n > 5 ? "big" : "small". The condition must be a real bool, so if n with a number or if s with a string won't compile, and both branches must give the same type.

Coming from JavaRust's if is an expression, so let size = if n > 5 { "big" } else { "small" }; replaces the ?: ternary, which Rust does not have. Rust never converts one branch to fit the other, where Java's ternary would pick a common supertype or promote a number.

Coming from C#if gives back a value, so it replaces C#'s ?:, which Rust does not have. Like C#, the condition must be a bool; unlike C#'s ?:, Rust never converts one branch to fit the other. Rust also drops the parentheses but always needs the braces.

Coming from CRust's if is an expression that replaces ?:, which Rust does not have. The condition must be a real bool (no if (n) on an integer or pointer), and both branches must have the same type, so a 0 can't stand in for a null string.

Coming from C++if is an expression, so it does the job of ?:, which Rust does not have. The condition must be a bool, with no implicit conversion from integers or pointers, and both branches must have exactly the same type.

The quick comparison

Rustsrc/main.rsRun ↗
fn main() {
let n = 7;
if n % 2 == 0 {
println!("{n} is even");
} else {
println!("{n} is odd");
}
// `if` gives back a value, so Rust has no `?:`
let size = if n > 5 { "big" } else { "small" };
println!("{size}");
}
$ cargo run
7 is odd
big
package main
import "fmt"
func main() {
n := 7
if n%2 == 0 {
fmt.Println(n, "is even")
} else {
fmt.Println(n, "is odd")
}
size := "small"
if n > 5 {
size = "big"
}
fmt.Println(size)
}
$ go run main.go
7 is odd
big
fun main() {
val n = 7
if (n % 2 == 0) {
println("$n is even")
} else {
println("$n is odd")
}
val size = if (n > 5) "big" else "small"
println(size)
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
7 is odd
big
n = 7
if n % 2 == 0:
print(f"{n} is even")
else:
print(f"{n} is odd")
# the conditional expression gives back a value
size = "big" if n > 5 else "small"
print(size)
$ python3 main.py
7 is odd
big
const n = 7;
if (n % 2 === 0) {
console.log(`${n} is even`);
} else {
console.log(`${n} is odd`);
}
// `if` is a statement in TypeScript; the ternary gives back a value
const size = n > 5 ? "big" : "small";
console.log(size);
$ tsc --strict --module nodenext --types node main.ts && node main.js
7 is odd
big
public class Main {
public static void main(String[] args) {
int n = 7;
if (n % 2 == 0) {
System.out.println(n + " is even");
} else {
System.out.println(n + " is odd");
}
// the ternary operator is Java's if-as-a-value
String size = n > 5 ? "big" : "small";
System.out.println(size);
}
}
$ javac Main.java && java Main
7 is odd
big
var n = 7;
if (n % 2 == 0)
{
Console.WriteLine($"{n} is even");
}
else
{
Console.WriteLine($"{n} is odd");
}
// the ?: operator gives back a value
var size = n > 5 ? "big" : "small";
Console.WriteLine(size);
$ dotnet run
7 is odd
big
#include <stdio.h>
int main(void) {
int n = 7;
if (n % 2 == 0) {
printf("%d is even\n", n);
} else {
printf("%d is odd\n", n);
}
// C's if does not give back a value; the ?: operator does
const char *size = n > 5 ? "big" : "small";
printf("%s\n", size);
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main
7 is odd
big
#include <iostream>
#include <string_view>
int main() {
int n = 7;
if (n % 2 == 0) {
std::cout << n << " is even\n";
} else {
std::cout << n << " is odd\n";
}
// the ternary operator gives back a value
std::string_view size = n > 5 ? "big" : "small";
std::cout << size << "\n";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main
7 is odd
big

What's different

  • The condition must be a bool. if n { } with a number does not compile. There is no "truthy" zero, so write if n != 0.
  • There is no ternary a ? b : c. if a { b } else { c } is the same thing.
  • Chain tests with else if. When every branch compares one value against fixed cases, match (a later lesson) is usually clearer.
  • if let and let ... else test whether a value has a given shape and name its parts in the same step. They replace "check, then unpack".
  • && and || stop as soon as the answer is known, so the right side runs only when it is needed.
Gotcha

Two branches that give different types

Looks fine: in Kotlin, Python, TypeScript, Java, C and C++ it runs fine. Rust won't compile it.

No direct equivalent in Go. Just a Rust rule.Works in Kotlin. In Rust it's just a rule.Works in Python. In Rust it's just a rule.Works in TypeScript. In Rust it's just a rule.Works in Java. In Rust it's just a rule.C# refuses this too. Same idea, different words.Works in C. In Rust it's just a rule.Works in C++. In Rust it's just a rule.
Rustsrc/main.rs✗ won't compile
fn main() {
let n = 7;
let size = if n > 5 { "big" } else { 0 };
println!("{size}");
}
Gono equivalent

Go has no if expression, so this can't come up. You declare size with one type first and assign to it in each branch.

fun main() {
val n = 7
val size = if (n > 5) "big" else 0
println(size)
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar✓ runs
big
n = 7
size = "big" if n > 5 else 0
print(size)
$ python3 main.py✓ runs
big
const n = 7;
const size = n > 5 ? "big" : 0; // type: "big" | 0
console.log(size);
$ tsc --strict --module nodenext --types node main.ts && node main.js✓ runs
big
public class Main {
public static void main(String[] args) {
int n = 7;
var size = n > 5 ? "big" : 0;
System.out.println(size);
}
}
$ javac Main.java && java Main✓ runs
big
C#✗ refuses too

C# refuses this too: the two sides of ?: must share a type, and there is no conversion between string and int (CS0173).

var n = 7;
var size = n > 5 ? "big" : 0;
Console.WriteLine(size);
Program.cs(2,12): error CS0173: Type of conditional expression cannot be determined because there is no implicit conversion between 'string' and 'int'
#include <stdio.h>
int main(void) {
int n = 7;
const char *size = n > 5 ? "big" : 0;
printf("%s\n", size);
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main✓ runs
big
#include <iostream>
int main() {
int n = 7;
auto size = n > 5 ? "big" : 0;
std::cout << size << "\n";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main✓ runs
big
What does the compiler say? Guess first, then open.
$ cargo buildWhat is E0308?
error[E0308]: `if` and `else` have incompatible types
 --> src/main.rs:3:42
  |
3 |     let size = if n > 5 { "big" } else { 0 };
  |                           -----          ^ expected `&str`, found integer
  |                           |
  |                           expected because of this
What happens in Go

Go has no if expression, so this can't come up. You declare size with one type first and assign to it in each branch.

What happens in Kotlin

Kotlin runs this and prints big. size quietly gets a loose common type instead of String, so later code can't use it as text without a check.

What happens in Python

Python runs this and prints big. The two branches can give different types, so size might be a string or an int and later code has to cope with both.

What happens in TypeScript

Prints big. TypeScript infers the union "big" | 0 and lets it through; every later use of size as a string then needs a typeof check or the compiler rejects it. Rust asks you to say so up front instead.

What happens in Java

Java compiles this and prints big. It picks a shared supertype for the two branches, so size ends up as a vague Object-like type that later code can't do much with.

What happens in C#

C# refuses this too: the two sides of ?: must share a type, and there is no conversion between string and int (CS0173).

What happens in C

C accepts this because a plain 0 counts as a null pointer. It prints big here, but with a smaller n the string would be NULL.

What happens in C++

This compiles because a literal 0 also counts as a null pointer, so both branches become const char*. It prints big, but if n were small cout would get a null pointer.

What Rust sees

Rust gives every variable exactly one type, decided while compiling. size cannot be "a string or maybe a number".

Why Rust says no

Code after this line uses size. If it could be either type, every later line would need to check which one it got. Rust makes both branches agree up front instead. If you really want "string or number", that is an enum, covered later.

The fix

Compiles and runs.

Rust, fixedsrc/main.rsRun ↗
fn main() {
let n = 7;
let size = if n > 5 { "big" } else { "small" };
println!("{size}");
}
$ cargo run
big

The toolkit

Statements and expressions showed how if looks, as a value, with the stray ; and the if x = 1 typo. This page is about how it behaves. Outputs come from Linux on 64-bit ARM with Rust 1.99.1.

if is an expression, and both arms agree

An if/else has a value, so it can sit wherever a value can (Statements and expressions has the rules). A branch may be a whole block; its last expression, with no ;, is the value.

Rustsrc/main.rsRun ↗
fn main() {
let n = 7;
// `if` as the value of a `let`
let size = if n > 5 { "big" } else { "small" };
println!("size: {size}");
// `if` as an argument, with no temporary variable
println!("parity: {}", if n % 2 == 0 { "even" } else { "odd" });
// a block as a branch: its last expression is the value
let label = if n > 5 {
let doubled = n * 2;
doubled + 1
} else {
0
};
println!("label: {label}");
}
$ cargo run
size: big
parity: odd
label: 15

Every branch must give the same type, because limit gets exactly one. Rust will not widen an integer to a float to match:

Rustsrc/main.rs✗ won't compile
fn main() {
let n = 7;
let limit = if n > 5 { 1 } else { 2.5 };
println!("limit: {limit}");
}
$ cargo buildWhat is E0308?
error[E0308]: `if` and `else` have incompatible types
 --> src/main.rs:3:39
  |
3 |     let limit = if n > 5 { 1 } else { 2.5 };
  |                            -          ^^^ expected integer, found floating-point number
  |                            |
  |                            expected because of this

The fix is 1.0: an integer and a float are different types, not one number. A branch that ends in return, continue or panic! is exempt, because it never gives a value (the never type). An if with no else gives (), so it cannot supply a value (E0317, in Smaller slips).

Only a bool is a condition

There is no truthiness: a number, a string, a list or an Option is not a condition (E0308, “expected bool”). The number case is in Smaller slips and the others are on What Rust lacks. Write the comparison you mean: n != 0, !name.is_empty(), found.is_some().

Comparing and combining

Rustsrc/main.rsRun ↗
fn main() {
let (a, b) = (3, 8);
println!("a == b: {}", a == b);
println!("a != b: {}", a != b);
println!("a < b: {}", a < b);
println!("a >= b: {}", a >= b);
println!("a > 1 && b > 1: {}", a > 1 && b > 1);
println!("a > 5 || b > 5: {}", a > 5 || b > 5);
println!("!(a < b): {}", !(a < b));
// `&` binds tighter than `==` in Rust, so no parentheses are needed here
println!("a & 1 == 1: {}", a & 1 == 1);
}
$ cargo run
a == b: false
a != b: true
a < b: true
a >= b: false
a > 1 && b > 1: true
a > 5 || b > 5: true
!(a < b): false
a & 1 == 1: true

== and != compare contents (numbers, text, tuples, arrays and many other types); <, >, <= and >= need a type with an order. Two numbers must have the same type, with no implicit widening (Numbers and types). A few mixed pairs, such as String and &str, are written to be comparable.

Rustsrc/main.rs✗ won't compile
fn main() {
let small: i32 = 1;
let big: i64 = 1;
if small == big {
println!("equal");
}
}
$ cargo buildWhat is E0308?
error[E0308]: mismatched types
 --> src/main.rs:4:17
  |
4 |     if small == big {
  |        -----    ^^^ expected `i32`, found `i64`
  |        |
  |        expected because this is `i32`
  |
help: you can convert `small` from `i32` to `i64`, matching the type of `big`
  |
4 |     if i64::from(small) == big {
  |        ++++++++++     +

Comparisons do not chain:

Rustsrc/main.rs✗ won't compile
fn main() {
let x = 3;
if 1 < x < 5 {
println!("between");
}
}
$ cargo build
error: comparison operators cannot be chained
 --> src/main.rs:3:10
  |
3 |     if 1 < x < 5 {
  |          ^   ^
  |
help: split the comparison into two
  |
3 |     if 1 < x && x < 5 {
  |              ++++

&& and || take bools. ! is “not” on a bool; on an integer it flips every bit instead, so write n == 0, not !n (Go spells the bit flip ^n). && and || short-circuit: the right side runs only if the left has not already decided the answer, which makes !items.is_empty() && items[0] > 5 safe. & and | also work on bool but run both sides:

Rustsrc/main.rsRun ↗
fn check(name: &str, answer: bool) -> bool {
println!(" checked {name}");
answer
}
// the right side indexes the slice, so it must only run when the slice is not empty
fn first_is_big(items: &[i32]) -> bool {
!items.is_empty() && items[0] > 5
}
fn main() {
println!("false && ...:");
let a = check("left", false) && check("right", true);
println!(" result: {a}");
println!("true || ...:");
let b = check("left", true) || check("right", false);
println!(" result: {b}");
println!("false & ... (no short-circuit):");
let c = check("left", false) & check("right", true);
println!(" result: {c}");
println!("first_is_big(&[]): {}", first_is_big(&[]));
println!("first_is_big(&[9, 1]): {}", first_is_big(&[9, 1]));
}
$ cargo run
false && ...:
  checked left
  result: false
true || ...:
  checked left
  result: true
false & ... (no short-circuit):
  checked left
  checked right
  result: false
first_is_big(&[]): false
first_is_big(&[9, 1]): true

(&[] and &[9, 1] are borrowed arrays: Borrowing.)

else if, and when to use match instead

if/else if/else tests in order and takes the first branch that is true. When the chain is the function’s value, as here, the last else is required, or the chain fails with the E0317 named in the first section (message in Smaller slips).

Rustsrc/main.rsRun ↗
fn describe(n: i32) -> &'static str {
if n < 0 {
"negative"
} else if n == 0 {
"zero"
} else if n < 10 {
"small"
} else {
"large"
}
}
fn main() {
for n in [-5, 0, 3, 10, 99] {
println!("{n}: {}", describe(n));
}
}
$ cargo run
-5: negative
0: zero
3: small
10: large
99: large

(&'static str is the type of a string literal: Reading signatures.)

When the branches compare one value with fixed cases or ranges, match is more direct and the compiler checks that no case is missing. A chain of ifs does not; use it when the tests are different questions.

if let and let ... else

if let asks “does this value have this shape?” and unpacks it in one step. The pattern goes left of the =. Here the value is the Result that parse gives back: Ok(number) or Err(reason) (Errors covers it). parse also rejects a number that does not fit the type, as "99999999999" shows for i32. Option (Option) and any enum (Enums) work the same way.

Rustsrc/main.rsRun ↗
fn main() {
// `parse` returns a Result: Ok(number) or Err(reason). Errors are covered later.
for text in ["42", "4x2", "99999999999"] {
if let Ok(n) = text.parse::<i32>() {
println!("{text:?} is the number {n}");
} else {
println!("{text:?} is not a number");
}
}
// `else if let` chains several patterns
let text = "7";
if let Ok(n) = text.parse::<u8>() {
println!("fits in a u8: {n}");
} else if let Ok(n) = text.parse::<i64>() {
println!("fits in an i64: {n}");
} else {
println!("not a whole number");
}
}
$ cargo run
"42" is the number 42
"4x2" is not a number
"99999999999" is not a number
fits in a u8: 7

The names a pattern binds exist only inside the if let block:

Rustsrc/main.rs✗ won't compile
fn main() {
if let Ok(v) = "5".parse::<i32>() {
println!("inside: {v}");
}
println!("outside: {v}");
}
$ cargo buildWhat is E0425?
error[E0425]: cannot find value `v` in this scope
 --> src/main.rs:5:25
  |
5 |     println!("outside: {v}");
  |                         ^ not found in this scope

let ... else is for the other case: the value must have the shape, or the code leaves this spot. The binding stays in scope after the statement, so the happy path stays unindented. The else block must leave (for example return, break, continue or panic!):

Rustsrc/main.rsRun ↗
fn double(text: &str) -> i32 {
let Ok(n) = text.parse::<i32>() else {
println!(" cannot parse {text:?}, using 0");
return 0;
};
// `n` is in scope for the rest of the function, not inside a block
n * 2
}
fn main() {
println!("double(\"21\"): {}", double("21"));
println!("double(\"abc\"): {}", double("abc"));
}
$ cargo run
double("21"): 42
  cannot parse "abc", using 0
double("abc"): 0
Rustsrc/main.rs✗ won't compile
fn main() {
let text = "abc";
let Ok(n) = text.parse::<i32>() else {
println!("not a number");
};
println!("n: {n}");
}
$ cargo buildWhat is E0308?
error[E0308]: `else` clause of `let...else` does not diverge
 --> src/main.rs:3:42
  |
3 |       let Ok(n) = text.parse::<i32>() else {
  |  __________________________________________^
4 | |         println!("not a number");
5 | |     };
  | |_____^ expected `!`, found `()`
  |
  = note:   expected type `!`
          found unit type `()`
  = help: try adding a diverging expression, such as `return` or `panic!(..)`
  = help: ...or use `match` instead of `let...else`

(Declarations and types shows the syntax.) Use if let when a mismatch means “do nothing”, let ... else when it means “stop here”. Both throw away the Err payload; to keep it, use match (Errors).

Comparing floats

Floats follow IEEE 754, as in most languages: results are not exact, and NaN (“not a number”) is neither smaller, equal nor larger than anything. Rust’s own point is the else.

Rustsrc/main.rsRun ↗
fn main() {
let sum = 0.1 + 0.2;
println!("0.1 + 0.2 == 0.3: {}", sum == 0.3);
println!("sum: {sum}");
println!("close enough: {}", (sum - 0.3_f64).abs() < 1e-9);
let nan = f64::NAN;
println!("nan == nan: {}", nan == nan);
println!("nan < 0.0: {}", nan < 0.0);
println!("nan > 0.0: {}", nan > 0.0);
println!("nan >= 0.0: {}", nan >= 0.0);
// both comparisons are false, so an `else` branch is not "the opposite"
if nan < 0.0 {
println!("negative");
} else {
println!("else branch: not negative, but not a number either");
}
}
$ cargo run
0.1 + 0.2 == 0.3: false
sum: 0.30000000000000004
close enough: true
nan == nan: false
nan < 0.0: false
nan > 0.0: false
nan >= 0.0: false
else branch: not negative, but not a number either

Test closeness with a tolerance you choose, not ==, and remember that the else of x < 0.0 is not “x is zero or more” when x might be NaN. Numbers and types covers the rest of float behaviour.

Your language, in Rust

Going deeper

Let chains

An if let can be joined with more tests by &&, and later tests can use names bound earlier. Let chains need edition 2024 (this course uses it): the Rust 1.88 release post says they are available only there.

Rustsrc/main.rsRun ↗
fn main() {
for text in ["42", "-3", "x"] {
if let Ok(n) = text.parse::<i32>()
&& n > 0
{
println!("{text:?}: positive number {n}");
} else {
println!("{text:?}: not a positive number");
}
}
}
$ cargo run
"42": positive number 42
"-3": not a positive number
"x": not a positive number

The pieces are joined with &&. A let cannot be joined with ||:

Rustsrc/main.rs✗ won't compile
fn main() {
let text = "5";
if let Ok(n) = text.parse::<i32>() || text.is_empty() {
println!("{n}");
}
}
$ cargo build
error: `||` operators are not supported in let chain conditions
 --> src/main.rs:3:40
  |
3 |     if let Ok(n) = text.parse::<i32>() || text.is_empty() {
  |                                        ^^

A bool as an Option

bool::then_some(value) gives Some(value) when the bool is true and None when it is false. It is close to if c { Some(v) } else { None } (Option), except that then_some evaluates its argument either way. Its sibling then takes a closure and runs it only when true (closures).

Rustsrc/main.rsRun ↗
fn costly() -> i32 {
println!("costly() ran");
1
}
fn main() {
let n = 12;
// `then_some` turns a bool into an Option: Some(value) when true, None when false
println!("{:?}", (n > 10).then_some("big"));
println!("{:?}", (n > 20).then_some("huge"));
// the argument is evaluated even when the bool is false
let early = (n > 20).then_some(costly());
println!("{:?}", early);
// the same with an if expression
let big = if n > 10 { Some("big") } else { None };
println!("{:?}", big);
}
$ cargo run
Some("big")
None
costly() ran
None
Some("big")

Practice

Try it

Each exercise has checks in main. Open it in the Playground, replace the todo!(), and run it until it prints all checks passed.

Classify a temperature

Write band (it returns &'static str, a string literal): below 0 is "freezing", 0 to 15 "cold", 16 to 25 "mild", 26 and above "hot". Make one if/else if/else chain the function’s value. The checks include the boundary values and the smallest and largest i32.

Starterexercises/classify.rs
// Return the band of a temperature in whole degrees Celsius:
// below 0 -> "freezing"
// 0 up to 15 -> "cold"
// 16 up to 25 -> "mild"
// 26 and above -> "hot"
// Use an `if` / `else if` chain as the function's value (no `return`).
fn band(celsius: i32) -> &'static str {
todo!("an if / else if / else chain whose last expression is the answer")
}
fn main() {
assert_eq!(band(-1), "freezing");
assert_eq!(band(0), "cold");
assert_eq!(band(15), "cold");
assert_eq!(band(16), "mild");
assert_eq!(band(25), "mild");
assert_eq!(band(26), "hot");
assert_eq!(band(i32::MIN), "freezing");
assert_eq!(band(i32::MAX), "hot");
println!("all checks passed");
}

Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.

Show a solution
One solutionexercises/classify.solution.rsRun ↗
// Return the band of a temperature in whole degrees Celsius:
// below 0 -> "freezing"
// 0 up to 15 -> "cold"
// 16 up to 25 -> "mild"
// 26 and above -> "hot"
// Use an `if` / `else if` chain as the function's value (no `return`).
fn band(celsius: i32) -> &'static str {
if celsius < 0 {
"freezing"
} else if celsius <= 15 {
"cold"
} else if celsius <= 25 {
"mild"
} else {
"hot"
}
}
fn main() {
assert_eq!(band(-1), "freezing");
assert_eq!(band(0), "cold");
assert_eq!(band(15), "cold");
assert_eq!(band(16), "mild");
assert_eq!(band(25), "mild");
assert_eq!(band(26), "hot");
assert_eq!(band(i32::MIN), "freezing");
assert_eq!(band(i32::MAX), "hot");
println!("all checks passed");
}
$ cargo run
all checks passed

Validate an age with let else

Write valid_age: text with optional whitespace around a whole number from 0 to 120 gives Some(number), anything else gives None. Use let ... else for the parse and a plain if for the range. The checks include "-5", "121", "4 2", an empty string and a number too big for an i32.

Starterexercises/valid-age.rs
// Turn text typed by a user into an age.
// Surrounding spaces are allowed. The age must be a whole number from 0 to 120.
// Anything else gives `None`. Use `let ... else` for the parse and a plain `if` for the range.
// (`Option` is covered in a later lesson: `Some(n)` is "there is a value", `None` is "there is not".)
fn valid_age(text: &str) -> Option<i32> {
todo!("parse with let-else and return None on failure, then check 0 to 120")
}
fn main() {
assert_eq!(valid_age("42"), Some(42));
assert_eq!(valid_age(" 7 "), Some(7));
assert_eq!(valid_age("0"), Some(0));
assert_eq!(valid_age("120"), Some(120));
assert_eq!(valid_age("121"), None);
assert_eq!(valid_age("-5"), None);
assert_eq!(valid_age("abc"), None);
assert_eq!(valid_age("4 2"), None);
assert_eq!(valid_age(""), None);
assert_eq!(valid_age("99999999999"), None);
println!("all checks passed");
}

Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.

Show a solution
One solutionexercises/valid-age.solution.rsRun ↗
// Turn text typed by a user into an age.
// Surrounding spaces are allowed. The age must be a whole number from 0 to 120.
// Anything else gives `None`. Use `let ... else` for the parse and a plain `if` for the range.
// (`Option` is covered in a later lesson: `Some(n)` is "there is a value", `None` is "there is not".)
fn valid_age(text: &str) -> Option<i32> {
let Ok(n) = text.trim().parse::<i32>() else {
return None;
};
if n < 0 || n > 120 { None } else { Some(n) }
}
fn main() {
assert_eq!(valid_age("42"), Some(42));
assert_eq!(valid_age(" 7 "), Some(7));
assert_eq!(valid_age("0"), Some(0));
assert_eq!(valid_age("120"), Some(120));
assert_eq!(valid_age("121"), None);
assert_eq!(valid_age("-5"), None);
assert_eq!(valid_age("abc"), None);
assert_eq!(valid_age("4 2"), None);
assert_eq!(valid_age(""), None);
assert_eq!(valid_age("99999999999"), None);
println!("all checks passed");
}
$ cargo run
all checks passed

Read more