Skip to content
Learn Rust
Theme

Variables

Basics · Lesson 8

Variables are declared with let. They cannot change unless you add mut.

Coming from Golet is your :=, but the variable can't change unless you write let mut. Writing let count = ... again in the same scope is allowed and makes a new variable, where Go's := would complain that nothing is new.

Coming from Kotlinlet is your val and let mut is your var, and let also freezes the contents: a val list still takes add, a Rust let list does not take push. The other difference is shadowing: you can write let count = ... again in the same scope, even with a new type, which Kotlin won't allow.

Coming from PythonAny Python name can be reassigned, but a Rust let can't change unless you write let mut. Writing let count = ... again makes a new variable, and its type is still checked at compile time.

Coming from TypeScriptRust's let is your const and let mut is your let. Unlike const, a plain let also stops you changing what's inside (pushing to a Vec, setting a field), and you may redeclare the same name in one scope. (References and Cell-style types are the exceptions; the page covers them.)

Coming from JavaRust flips Java's default: let is final var, and you add mut to get a plain changeable local. Rust's let also freezes the contents, where a final list still takes add. Rust also lets you shadow a name with a new let, which Java forbids for locals.

Coming from C#let is a local you cannot reassign, which C# has no keyword for (const only works for compile-time constants, and locals can't be readonly). let mut is your everyday var, and a second let with the same name shadows the first, which C# rejects.

Coming from Clet is const int, and let mut is a plain int, so the default is the reverse of C. Shadowing is allowed even in the same block, where C would report a redeclaration.

Coming from C++let x is like const auto x, and let mut x like plain auto x, so Rust makes const the default. The difference: auto reads only the right-hand side, and let can wait for a later line to decide the type. Shadowing with a second let is allowed in the same scope, which C++ only allows in an inner block.

The quick comparison

Rustsrc/main.rsRun ↗
fn main() {
let name = "gopher"; // cannot change
let mut count = 1; // mut: can change
count += 1;
let count = count * 10; // shadowing: a NEW variable with the same name
println!("{name} {count}");
}
$ cargo run
gopher 20
package main
import "fmt"
func main() {
name := "gopher"
count := 1
count += 1
count = count * 10
fmt.Println(name, count)
}
$ go run main.go
gopher 20
fun main() {
val name = "gopher"
var count = 1
count += 1
count = count * 10
println("$name $count")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
gopher 20
name = "gopher"
count = 1
count += 1
count = count * 10 # Python just reassigns; there is no separate "shadowing"
print(f"{name} {count}")
$ python3 main.py
gopher 20
const mascot = "gopher"; // const: cannot be reassigned
let count = 1; // let: can change
count += 1;
// TypeScript has no shadowing in the same scope, so use a new name
const scaled = count * 10;
console.log(`${mascot} ${scaled}`);
$ tsc --strict --module nodenext --types node main.ts && node main.js
gopher 20
public class Main {
public static void main(String[] args) {
final var name = "gopher"; // final: cannot change
var count = 1; // locals can change by default
count += 1;
count = count * 10; // a local cannot reuse a local's name in Java: reassign instead
System.out.println(name + " " + count);
}
}
$ javac Main.java && java Main
gopher 20
const string name = "gopher"; // cannot change
var count = 1; // var: can change
count += 1;
// C# does not let a local reuse a local's name, so the new value gets a new name
var total = count * 10;
Console.WriteLine($"{name} {total}");
$ dotnet run
gopher 20
#include <stdio.h>
int main(void) {
const char *const name = "gopher"; // neither the pointer nor the text can change
int count = 1; // C variables can change by default
count += 1;
count = count * 10; // C has no shadowing in the same block: reuse it
printf("%s %d\n", name, count);
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main
gopher 20
#include <iostream>
#include <string>
int main() {
const std::string name = "gopher"; // const: cannot change
int count = 1; // plain variables can change
count += 1;
const int total = count * 10; // no shadowing in the same scope: pick a new name
std::cout << name << " " << total << "\n";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main
gopher 20

What's different

  • Plain let can't change. You opt in with let mut, so a reader can see at a glance which variables change.
  • Shadowing (let count = ... again) makes a new variable that hides the old one. It can even have a different type. It is common for steps like let input = input.trim();.
  • const is for a value known before the program runs. It always needs a type and may sit outside any function, and by convention its name is SHOUTING_CASE.
  • A variable you never read gets a compiler warning. Start its name with _ to say that is on purpose.
Gotcha

Changing a variable without mut

Looks fine: in Go, Java, C#, C and C++ it runs fine. Rust won't compile it.

Works in Go. In Rust it's just a rule.Kotlin refuses this too. Same idea, different words.No direct equivalent in Python. Just a Rust rule.TypeScript refuses this too. Same idea, different words.Works in Java. In Rust it's just a rule.Works in C#. In Rust it's just a rule.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 count = 1;
count += 1;
println!("{count}");
}
package main
import "fmt"
func main() {
count := 1
count += 1
fmt.Println(count)
}
$ go run main.go✓ runs
2
Kotlin✗ refuses too

Kotlin refuses this too if you write val count = 1; with var it runs and prints 2. Rust's let is Kotlin's val, and let mut is var. What is new is that Rust's let also stops the push on a list, as the page shows.

fun main() {
val count = 1
count += 1
println(count)
}
Main.kt:3:5: error: 'val' cannot be reassigned.
    count += 1
    ^^^^^
Pythonno equivalent

Python has no mut: every variable can be reassigned, so this mistake can't happen.

TypeScript✗ refuses too

TypeScript refuses this too if you write const: you cannot assign to a constant. Rust's let is TypeScript's const, and let mut is let.

const count = 1;
count += 1;
console.log(count);
main.ts(2,1): error TS2588: Cannot assign to 'count' because it is a constant.
public class Main {
public static void main(String[] args) {
var count = 1;
count += 1;
System.out.println(count);
}
}
$ javac Main.java && java Main✓ runs
2
var count = 1;
count += 1;
Console.WriteLine(count);
$ dotnet run✓ runs
2
#include <stdio.h>
int main(void) {
int count = 1;
count += 1;
printf("%d\n", count);
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main✓ runs
2
#include <iostream>
int main() {
int count = 1;
count += 1;
std::cout << count << "\n";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main✓ runs
2
What does the compiler say? Guess first, then open.
$ cargo buildWhat is E0384?
error[E0384]: cannot assign twice to immutable variable `count`
 --> src/main.rs:3:5
  |
2 |     let count = 1;
  |         ----- first assignment to `count`
3 |     count += 1;
  |     ^^^^^^^^^^ cannot assign twice to immutable variable
  |
help: consider making this binding mutable
  |
2 |     let mut count = 1;
  |         +++
What happens in Go

Go runs this and prints 2. A variable made with := can always be changed later.

What happens in Kotlin

Kotlin refuses this too if you write val count = 1; with var it runs and prints 2. Rust's let is Kotlin's val, and let mut is var. What is new is that Rust's let also stops the push on a list, as the page shows.

What happens in Python

Python has no mut: every variable can be reassigned, so this mistake can't happen.

What happens in TypeScript

TypeScript refuses this too if you write const: you cannot assign to a constant. Rust's let is TypeScript's const, and let mut is let.

What happens in Java

Java locals can change by default, so this prints 2. Write final var count = 1 and javac refuses it, like Rust's let.

What happens in C#

A plain var can always change in C#, so this prints 2. Only const and readonly fields are locked, and local variables can't be readonly.

What happens in C

C variables can change unless you mark them const, so this prints 2. Rust flips the default: you opt in to change with mut.

What happens in C++

C++ variables can change unless you mark them const, so this prints 2. Rust flips the default: you opt in to change with mut.

What Rust sees

In Rust, plain let means "this never changes". You only get a changeable variable when you ask for one with mut.

Why Rust says no

Rust chose this default so that a reader can see which variables change. The Rust Book's reason: code that assumes a value never changes can break when other code changes it. When you read let x, that name is not assigned again; only the mut ones need watching.

The fix

Compiles and runs.

Rust, fixedsrc/main.rsRun ↗
fn main() {
let mut count = 1;
count += 1;
println!("{count}");
}
$ cargo run
2

The toolkit

The Syntax part showed how let, const and static are spelled. This lesson is about what they do. Outputs come from Linux on 64-bit ARM with Rust 1.99.1.

Frozen unless you say mut

A let variable cannot be assigned to once it has a value (E0384, in the gotcha above); mut lifts that. It also freezes the value the variable owns.

Rustsrc/main.rs✗ won't compile
fn main() {
let list = Vec::new();
list.push(1);
println!("len: {}", list.len());
}
$ cargo buildWhat is E0596?
error[E0596]: cannot borrow `list` as mutable, as it is not declared as mutable
 --> src/main.rs:3:5
  |
3 |     list.push(1);
  |     ^^^^ cannot borrow as mutable
  |
help: consider changing this to be mutable
  |
2 |     let mut list = Vec::new();
  |         +++

E0596. push changes the list, and its variable is not mut. The message says “borrow as mutable”: a method that changes a value borrows it mutably (Borrowing; Vec is a growable list, Vectors). References and Cell-style types are the exceptions (Going deeper).

Why is this the default? The Rust Book’s reason: if one part of the code assumes a value never changes and another changes it, the first part may misbehave. With plain let a reader need not check; only let mut variables need watching.

Shadowing or mut?

Writing let again with the same name makes a new variable and hides the old one (spelling). The two tools do different jobs:

let mut x then x = ... let x = ... again
Variable the same one a new one
Type must stay the same may change
Inside a block the change outlives the block the new variable ends with the block
Afterwards still mut frozen again, unless you write let mut
Rustsrc/main.rsRun ↗
fn main() {
let mut a = 1;
let b = 1;
println!("before: a = {a}, b = {b}");
{
a = 10; // changes the one variable
let b = 10; // a new variable, gone when the block ends
println!("inside: a = {a}, b = {b}");
}
println!("after: a = {a}, b = {b}");
}
$ cargo run
before: a = 1, b = 1
inside: a = 10, b = 10
after: a = 10, b = 1

a was changed for good. b was hidden inside the block and came back after it. A new variable can have a new type (these two sizes are two variables):

Rustsrc/main.rsRun ↗
fn main() {
let speed = 90; // i32
println!("first: {} bytes", size_of_val(&speed));
let speed = speed as f64 * 1.6; // a new variable: f64
println!("second: {} bytes, {speed}", size_of_val(&speed));
}
$ cargo run
first: 4 bytes
second: 8 bytes, 144

Assigning a different type to a mut variable is an error:

Rustsrc/main.rs✗ won't compile
fn main() {
let mut speed = 90;
speed = speed as f64 * 1.6;
println!("{speed}");
}
$ cargo buildWhat is E0308?
error[E0308]: mismatched types
 --> src/main.rs:3:13
  |
2 |     let mut speed = 90;
  |                     -- expected due to this value
3 |     speed = speed as f64 * 1.6;
  |             ^^^^^^^^^^^^^^^^^^ expected integer, found `f64`

Use mut for something that really changes, such as a counter, and shadowing for a step that makes a new version of the same thing (let input = input.trim();, a conversion).

const, static and let

let const static
Value any expression, computed when the line runs a constant expression, computed before the program runs a constant expression
Can change only with mut never not by assignment (atomic types and Mutex can change), and static mut needs unsafe for every use
In memory a variable copied into each place that uses it one place; all references point to it

The spelling, where each may sit are on Declarations and types, and the naming is under Naming. The “In memory” row is the Rust Reference on constants and statics. The programs below run the other rows (static mut is in what Rust lacks).

Rustsrc/main.rsRun ↗
const SECONDS_PER_DAY: u32 = 24 * 60 * 60;
static APP_NAME: &str = "tracker";
fn main() {
const LIMIT: u32 = 3; // a const may sit inside a function
let days = 2;
let seconds = days * SECONDS_PER_DAY;
println!("{APP_NAME}: {seconds} seconds in {days} days (limit {LIMIT})");
}
$ cargo run
tracker: 172800 seconds in 2 days (limit 3)

A const cannot use a runtime value, even one that looks fixed:

Rustsrc/main.rs✗ won't compile
fn main() {
let n = 3;
const LIMIT: i32 = n * 2;
println!("{LIMIT}");
}
$ cargo buildWhat is E0435?
error[E0435]: attempt to use a non-constant value in a constant
 --> src/main.rs:3:24
  |
3 |     const LIMIT: i32 = n * 2;
  |                        ^ non-constant value
  |
help: consider using `let` instead of `const`
  |
3 -     const LIMIT: i32 = n * 2;
3 +     let LIMIT: i32 = n * 2;

E0435. The help: suggests let, which is what you want for a value that is not known in advance. Nor can a static be assigned to:

Rustsrc/main.rs✗ won't compile
static COUNT: u32 = 0;
fn main() {
COUNT += 1;
println!("{COUNT}");
}
$ cargo buildWhat is E0594?
error[E0594]: cannot assign to immutable static item `COUNT`
 --> src/main.rs:4:5
  |
1 | static COUNT: u32 = 0;
  | ----------------- this `static` cannot be written to
...
4 |     COUNT += 1;
  |     ^^^^^^^^^^ cannot assign

A static can sit inside a function and keep its value between calls. It changes through a type made for that, here AtomicU32 (Shared state):

Rustsrc/main.rsRun ↗
use std::sync::atomic::{AtomicU32, Ordering};
fn next() -> u32 {
static CALLS: AtomicU32 = AtomicU32::new(0); // a static inside a function
CALLS.fetch_add(1, Ordering::Relaxed) + 1 // changes without `mut` or `unsafe`
}
fn main() {
println!("calls: {} {} {}", next(), next(), next());
}
$ cargo run
calls: 1 2 3

Reach for const first; use a static when the value should live in one place for the whole program. Mutable global state is awkward on purpose: see what Rust lacks.

Variables you do not use

An unused variable and a mut that is never needed are warnings, not errors: the lints unused_variables and unused_mut are on by default. #![deny(unused)] (lint levels) turns them into errors for the whole file:

Rustsrc/main.rs✗ won't compile
#![deny(unused)]
fn main() {
let mut total = 10;
let scratch = 5;
println!("total: {total}");
}
$ cargo build
error: variable does not need to be mutable
 --> src/main.rs:4:9
  |
4 |     let mut total = 10;
  |         ----^^^^^
  |         |
  |         help: remove this `mut`
  |
note: the lint level is defined here
 --> src/main.rs:1:9
  |
1 | #![deny(unused)]
  |         ^^^^^^
  = note: `#[deny(unused_mut)]` implied by `#[deny(unused)]`

error: unused variable: `scratch`
 --> src/main.rs:5:9
  |
5 |     let scratch = 5;
  |         ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_scratch`
  |
  = note: `#[deny(unused_variables)]` implied by `#[deny(unused)]`

For “unused on purpose”, start the name with an underscore. _kept is still a variable; _ alone binds nothing:

Rustsrc/main.rsRun ↗
fn main() {
let _scratch = 5; // never read: no warning
let _ = 6; // binds nothing
let _kept = 7; // still a real variable
println!("kept: {_kept}");
}
$ cargo run
kept: 7

Declared now, set later

A let without a value is allowed (the working form), but a read that some path reaches unset is E0381:

Rustsrc/main.rs✗ won't compile
fn main() {
let label;
let n = 3;
if n > 2 {
label = "big";
}
println!("label: {label}");
}
$ cargo buildWhat is E0381?
error[E0381]: used binding `label` is possibly-uninitialized
 --> src/main.rs:7:23
  |
2 |     let label;
  |         ----- binding declared here but left uninitialized
3 |     let n = 3;
4 |     if n > 2 {
  |        ----- if this `if` condition is `false`, `label` is not initialized
5 |         label = "big";
6 |     }
  |      - an `else` arm might be missing here, initializing `label`
7 |     println!("label: {label}");
  |                       ^^^^^ `label` used here but it is possibly-uninitialized
  |
  = note: when checking initialization, the compiler describes possible control-flow paths without evaluating whether branch conditions can actually have the values shown

A plain let takes its value once; a second assignment is E0384 again:

Rustsrc/main.rs✗ won't compile
fn main() {
let label;
label = "first";
label = "second";
println!("label: {label}");
}
$ cargo buildWhat is E0384?
error[E0384]: cannot assign twice to immutable variable `label`
 --> src/main.rs:4:5
  |
3 |     label = "first";
  |     --------------- first assignment to `label`
4 |     label = "second";
  |     ^^^^^^^^^^^^^^^^ cannot assign twice to immutable variable
  |
help: consider making this binding mutable
  |
2 |     let mut label;
  |         +++

Inference looks ahead

A later use can decide a variable’s type: let a = 5; becomes a u8 if a takes_u8(a) call follows (the type is often optional). If two uses disagree, the error is at the second:

Rustsrc/main.rs✗ won't compile
fn takes_u8(n: u8) -> u8 {
n
}
fn takes_u64(n: u64) -> u64 {
n
}
fn main() {
let a = 5;
takes_u8(a);
takes_u64(a);
}
$ cargo buildWhat is E0308?
error[E0308]: mismatched types
  --> src/main.rs:12:15
   |
12 |     takes_u64(a);
   |     --------- ^ expected `u64`, found `u8`
   |     |
   |     arguments to this function are incorrect
   |
note: function defined here
  --> src/main.rs:5:4
   |
 5 | fn takes_u64(n: u64) -> u64 {
   |    ^^^^^^^^^ ------
help: you can convert a `u8` to a `u64`
   |
12 |     takes_u64(a.into());
   |                +++++++

Your language, side by side

Going deeper

mut belongs to the name, not the value

Whether a value can change depends on its variable: move it into a mut variable and it can change (“moved” is Ownership):

Rustsrc/main.rsRun ↗
fn main() {
let first = String::from("ab");
let mut second = first; // the text moves into a variable that may change
second.push('c');
println!("second: {second}");
}
$ cargo run
second: abc

The reverse works too. Shadowing a mut variable with a plain let freezes it again, a way to say “built, now finished”:

Rustsrc/main.rs✗ won't compile
fn main() {
let mut list = Vec::new();
list.push(1);
list.push(2);
let list = list; // the same list, now frozen
list.push(3);
}
$ cargo buildWhat is E0596?
error[E0596]: cannot borrow `list` as mutable, as it is not declared as mutable
 --> src/main.rs:6:5
  |
6 |     list.push(3);
  |     ^^^^ cannot borrow as mutable
  |
help: consider changing this to be mutable
  |
5 |     let mut list = list; // the same list, now frozen
  |         +++

Two things still change a value through a variable that is not mut. A &mut reference changes what it points at (Borrowing):

Rustsrc/main.rsRun ↗
fn main() {
let mut v = vec![1];
let r = &mut v; // `r` is not `mut`, but it points at a value that is
r.push(2);
println!("v: {v:?}");
}
$ cargo run
v: [1, 2]

And types such as Cell are built to change through a plain let (Smart pointers covers them):

Rustsrc/main.rsRun ↗
use std::cell::Cell;
fn main() {
let counter = Cell::new(0); // no `mut`
counter.set(counter.get() + 1);
println!("counter: {}", counter.get());
}
$ cargo run
counter: 1

What a const may contain

A const is computed before the program runs, so it can call only const fn functions:

Rustsrc/main.rsRun ↗
const fn square(n: u32) -> u32 {
n * n
}
const AREA: u32 = square(4);
fn main() {
println!("area: {AREA}");
}
$ cargo run
area: 16

String::from is not one, so this fails with E0015:

Rustsrc/main.rs✗ won't compile
const NAME: String = String::from("tracker");
fn main() {
println!("{NAME}");
}
$ cargo buildWhat is E0015?
error[E0015]: cannot call non-const associated function `<String as From<&str>>::from` in constants
 --> src/main.rs:1:22
  |
1 | const NAME: String = String::from("tracker");
  |                      ^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: calls in constants are limited to constant functions, tuple structs and tuple variants

For a fixed piece of text, use &str (const NAME: &str = "tracker";).

Shadowing can hide a mistake

Shadowing inside a block does not touch the outer variable, and the compiler cannot know you meant to update it. The program below compiles and prints total: 0:

Rustsrc/main.rsRun ↗
#![allow(unused)] // warnings off, to show what the program does
fn main() {
let mut total = 0;
for n in 1..=3 {
let total = total + n; // meant: total += n
}
println!("total: {total}");
}
$ cargo run
total: 0

That file switches the warnings off. With them on, the compiler notices the inner total is never used and the outer mut never needed:

Rustsrc/main.rs✗ won't compile
#![deny(unused)]
fn main() {
let mut total = 0;
for n in 1..=3 {
let total = total + n; // meant: total += n
}
println!("total: {total}");
}
$ cargo build
error: variable does not need to be mutable
 --> src/main.rs:4:9
  |
4 |     let mut total = 0;
  |         ----^^^^^
  |         |
  |         help: remove this `mut`
  |
note: the lint level is defined here
 --> src/main.rs:1:9
  |
1 | #![deny(unused)]
  |         ^^^^^^
  = note: `#[deny(unused_mut)]` implied by `#[deny(unused)]`

error: unused variable: `total`
 --> src/main.rs:6:13
  |
6 |         let total = total + n; // meant: total += n
  |             ^^^^^ help: if this is intentional, prefix it with an underscore: `_total`
  |
  = note: `#[deny(unused_variables)]` implied by `#[deny(unused)]`

Read warnings about unused variables. Here one is the bug.

Practice

Try it

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

Fix a counter

This counts the multiples of step from 1 to limit, and the answer is wrong. The compiler already warns about the cause: read its warnings.

Starterexercises/multiples.rs
/// How many numbers from 1 to `limit` (inclusive) are multiples of `step`?
/// A `step` of 0 has no multiples, so the answer is 0.
/// This version compiles but gives the wrong answer. Read the compiler's warnings: they point at the bug.
fn count_multiples(limit: u32, step: u32) -> u32 {
if step == 0 {
return 0;
}
let mut count = 0;
for n in 1..=limit {
if n % step == 0 {
let count = count + 1;
}
}
count
}
fn main() {
assert_eq!(count_multiples(10, 3), 3); // 3, 6, 9
assert_eq!(count_multiples(10, 1), 10);
assert_eq!(count_multiples(2, 5), 0);
assert_eq!(count_multiples(0, 3), 0);
// The case to watch: a step of 0 must not crash.
assert_eq!(count_multiples(10, 0), 0);
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/multiples.solution.rsRun ↗
/// How many numbers from 1 to `limit` (inclusive) are multiples of `step`?
/// A `step` of 0 has no multiples, so the answer is 0.
fn count_multiples(limit: u32, step: u32) -> u32 {
if step == 0 {
return 0;
}
let mut count = 0;
for n in 1..=limit {
if n % step == 0 {
count += 1;
}
}
count
}
fn main() {
assert_eq!(count_multiples(10, 3), 3); // 3, 6, 9
assert_eq!(count_multiples(10, 1), 10);
assert_eq!(count_multiples(2, 5), 0);
assert_eq!(count_multiples(0, 3), 0);
// The case to watch: a step of 0 must not crash.
assert_eq!(count_multiples(10, 0), 0);
println!("all checks passed");
}
$ cargo run
all checks passed

Shadow to convert

Return the average of total over count as an f64. Shadow each parameter with a converted copy (let total = total as f64;) and divide. No items must give 0.0, not NaN.

Starterexercises/average.rs
/// The average of `total` spread over `count` items, as a float.
/// With no items the average is 0.0.
fn average(total: i32, count: i32) -> f64 {
todo!(
"shadow both parameters with `let total = total as f64;` (and the same for count), then divide. Check count == 0 first: 0.0 / 0.0 is NaN"
)
}
fn main() {
assert_eq!(average(7, 2), 3.5);
assert_eq!(average(10, 5), 2.0);
assert_eq!(average(-3, 2), -1.5);
// The case to watch: no items must give 0.0, not NaN.
assert_eq!(average(0, 0), 0.0);
assert_eq!(average(5, 0), 0.0);
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/average.solution.rsRun ↗
/// The average of `total` spread over `count` items, as a float.
/// With no items the average is 0.0.
fn average(total: i32, count: i32) -> f64 {
if count == 0 {
return 0.0;
}
let total = total as f64;
let count = count as f64;
total / count
}
fn main() {
assert_eq!(average(7, 2), 3.5);
assert_eq!(average(10, 5), 2.0);
assert_eq!(average(-3, 2), -1.5);
// The case to watch: no items must give 0.0, not NaN.
assert_eq!(average(0, 0), 0.0);
assert_eq!(average(5, 0), 0.0);
println!("all checks passed");
}
$ cargo run
all checks passed

Read more