Rust at a glance
Syntax · Lesson 1
Rust code is built from a small set of symbols that repeat on every page: fn, let, &, ::, ?, !. This page names each one, so you can read the shape of a file before you know what every piece does.
Coming from Gofunc is fn, and := is let mut: a plain let makes a variable that cannot change. Types still follow the name, but with a colon (a: i32), the return type goes after ->, and you write the semicolons that Go adds for you. The last line of a block, without a ;, is the block's value.
Coming from Kotlinfun is fn, val is let and var is let mut. name: Type is the same, but the return type goes after ->, not :. A Rust function whose body is one last expression, written without ;, is like your = a + b form, with braces.
Coming from PythonBlocks use { } and lines end in ;; indentation is only for people. Comments start with //, not #, and 'a' in single quotes is one character, so text uses double quotes. A variable needs let, and let mut to change. println!("{name}") reads like an f-string.
Coming from TypeScriptTypes follow names with a colon, as in TypeScript, but function is fn and the return type goes after ->. Rust's plain let is like your const, and let mut is your let.
Coming from JavaThere is no class around main and no public static: functions stand alone. The type follows the name (a: i32), and a variable cannot change unless you write let mut; plain let is like final var. The last expression of a block, written without ;, is its value, so by habit return is kept for leaving early (a final return x; still compiles).
Coming from C#Functions stand alone, as with top-level statements. The type follows the name (a: i32), and var becomes let mut (plain let when it never changes). Inside println!, {name} works without a $ for a plain variable name. A block's last expression, written without ;, is its value.
Coming from CThe type follows the name (a: i32, return type after ->), plain let is like const, and there is no header to include. The ! in println! marks a macro call, not a function. A block's last expression, written without ;, is its value.
Coming from C++Only the result type matches your trailing return type (auto add(int a, int b) -> int): Rust writes parameter types after the names, a: i32. let is like const auto, let mut like plain auto, and . works through a reference, so there is no -> for members.
The quick comparison
// Greet someone, then add up 1 to 4.fn greet(name: &str) -> String { format!("Hello, {name}!")}
fn add(a: i32, b: i32) -> i32 { a + b}
fn main() { println!("{}", greet("Ferris")); let mut total = 0; for n in 1..=4 { total = add(total, n); } println!("total: {total}");}Hello, Ferris! total: 10
package main
import "fmt"
// Greet someone, then add up 1 to 4.func greet(name string) string { return fmt.Sprintf("Hello, %s!", name)}
func add(a, b int) int { return a + b}
func main() { fmt.Println(greet("Ferris")) total := 0 for n := 1; n <= 4; n++ { total = add(total, n) } fmt.Printf("total: %d\n", total)}Hello, Ferris! total: 10
// Greet someone, then add up 1 to 4.fun greet(name: String): String = "Hello, $name!"
fun add(a: Int, b: Int): Int = a + b
fun main() { println(greet("Ferris")) var total = 0 for (n in 1..4) { total = add(total, n) } println("total: $total")}Hello, Ferris! total: 10
# Greet someone, then add up 1 to 4.def greet(name: str) -> str: return f"Hello, {name}!"
def add(a: int, b: int) -> int: return a + b
print(greet("Ferris"))total = 0for n in range(1, 5): total = add(total, n)print(f"total: {total}")Hello, Ferris! total: 10
// Greet someone, then add up 1 to 4.function greet(name: string): string { return `Hello, ${name}!`;}
function add(a: number, b: number): number { return a + b;}
console.log(greet("Ferris"));let total = 0;for (let n = 1; n <= 4; n++) { total = add(total, n);}console.log(`total: ${total}`);Hello, Ferris! total: 10
public class Main { // Greet someone, then add up 1 to 4. static String greet(String name) { return "Hello, " + name + "!"; }
static int add(int a, int b) { return a + b; }
public static void main(String[] args) { System.out.println(greet("Ferris")); int total = 0; for (int n = 1; n <= 4; n++) { total = add(total, n); } System.out.println("total: " + total); }}Hello, Ferris! total: 10
// Greet someone, then add up 1 to 4.static string Greet(string name) => $"Hello, {name}!";
static int Add(int a, int b) => a + b;
Console.WriteLine(Greet("Ferris"));var total = 0;for (var n = 1; n <= 4; n++){ total = Add(total, n);}Console.WriteLine($"total: {total}");Hello, Ferris! total: 10
#include <stdio.h>
// Greet someone, then add up 1 to 4.void greet(const char *name) { printf("Hello, %s!\n", name);}
int add(int a, int b) { return a + b;}
int main(void) { greet("Ferris"); int total = 0; for (int n = 1; n <= 4; n++) { total = add(total, n); } printf("total: %d\n", total); return 0;}Hello, Ferris! total: 10
#include <iostream>#include <string>
// Greet someone, then add up 1 to 4.std::string greet(const std::string& name) { return "Hello, " + name + "!";}
int add(int a, int b) { return a + b;}
int main() { std::cout << greet("Ferris") << "\n"; int total = 0; for (int n = 1; n <= 4; n++) { total = add(total, n); } std::cout << "total: " << total << "\n";}Hello, Ferris! total: 10
What's different
- A name followed by
!is a macro call, such asprintln!andformat!. It looks like a function call, and the!is there so you can tell. - A type follows its name after a colon:
name: &str,a: i32. The return type follows an arrow:-> String. - A line ending in
;is a statement. The last line of a block without a;is the value of the block, which is howgreetreturns its text with noreturn. 1..=4counts 1, 2, 3, 4, and1..4stops before 4.
Changing a variable without mut
Looks fine: in Go, Python, 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.Works in Python. In Rust it's just a 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.fn main() { let total = 0; for n in 1..=4 { total += n; } println!("total: {total}");}package main
import "fmt"
func main() { total := 0 for n := 1; n <= 4; n++ { total += n } fmt.Printf("total: %d\n", total)}total: 10
Kotlin refuses this program too: a val cannot be reassigned. With var it prints total: 10. Rust's let is val, and let mut is var.
fun main() { val total = 0 for (n in 1..4) { total += n } println("total: $total")}Main.kt:4:9: error: 'val' cannot be reassigned.
total += n
^^^^^total = 0for n in range(1, 5): total += nprint(f"total: {total}")total: 10
TypeScript refuses this too: a const cannot be assigned to. With let it prints total: 10. Rust's let is like const, and let mut is like let.
const total = 0;for (let n = 1; n <= 4; n++) { total += n;}console.log(`total: ${total}`);main.ts(3,3): error TS2588: Cannot assign to 'total' because it is a constant.
public class Main { public static void main(String[] args) { int total = 0; for (int n = 1; n <= 4; n++) { total += n; } System.out.println("total: " + total); }}total: 10
var total = 0;for (var n = 1; n <= 4; n++){ total += n;}Console.WriteLine($"total: {total}");total: 10
#include <stdio.h>
int main(void) { int total = 0; for (int n = 1; n <= 4; n++) { total += n; } printf("total: %d\n", total); return 0;}total: 10
#include <iostream>
int main() { int total = 0; for (int n = 1; n <= 4; n++) { total += n; } std::cout << "total: " << total << "\n";}total: 10
What does the compiler say? Guess first, then open.
error[E0384]: cannot assign twice to immutable variable `total` --> src/main.rs:4:9 | 2 | let total = 0; | ----- first assignment to `total` 3 | for n in 1..=4 { 4 | total += n; | ^^^^^^^^^^ cannot assign twice to immutable variable | help: consider making this binding mutable | 2 | let mut total = 0; | +++
Go prints total: 10. A variable made with := can always be changed later; Rust's twin of := is let mut.
Kotlin refuses this program too: a val cannot be reassigned. With var it prints total: 10. Rust's let is val, and let mut is var.
Python prints total: 10. Every name can be rebound, so there is no mut to forget.
TypeScript refuses this too: a const cannot be assigned to. With let it prints total: 10. Rust's let is like const, and let mut is like let.
Java prints total: 10. A local can change unless you write final.
C# prints total: 10. A var local can always be changed later.
C prints total: 10. Variables can change unless you write const.
C++ prints total: 10. Variables can change unless you write const.
Plain let means the name keeps its value. total += n changes it, so the compiler stops at that line and asks for mut.
This is a default, not a safety proof. When you read let total anywhere in Rust code, you know it keeps its value for the rest of the block. Only the let mut variables need watching.
The fix
Compiles and runs.
fn main() { let mut total = 0; for n in 1..=4 { total += n; } println!("total: {total}");}total: 10
The toolkit
This page is a map, not a lesson. Each symbol gets a name, a one-sentence meaning, and a link to the lesson that teaches it properly. You will not understand every meaning yet, and you do not need to. Coming from another language? There is a phrasebook for C and C++, C#, Go, Java, Kotlin, Python and TypeScript. The aim is to read the shape of a Rust file: what is a function, what is a type, what is a call, what is a block. Outputs on this page come from Linux on 64-bit ARM with Rust 1.99.1.
One program, whole
This program counts the words in some text and reports on them. It uses most of the syntax you will see everywhere. The numbered comments match the table below it.
// (1) Count the words in some text and report on them.// (2)#[derive(Debug)]// (3)struct Report { words: usize, longest: String,}
// (4)fn analyze(text: &str) -> Result<Report, String> { // (5) let mut longest = String::new(); let mut words = 0; // (6) for word in text.split_whitespace() { words += 1; // (7) if word.len() > longest.len() { longest = word.to_string(); } } if words == 0 { // (8) return Err(String::from("no words")); } // (9) Ok(Report { words, longest })}
// (10)fn main() -> Result<(), String> { // (11) let report = analyze("the quick brown fox")?; // (12) println!("{report:?}"); // (13) let shout = |s: &str| s.to_uppercase(); // (14) match report.words { 0 => println!("empty"), 1..=3 => println!("short"), n => println!("{n} words"), } println!("longest: {}", shout(&report.longest)); Ok(())}Report { words: 4, longest: "quick" }
4 words
longest: QUICKThree things help while reading it:
- A file is a list of items (
struct,fn). Each item’s contents sit in{ }, and indentation is only for readers. The program starts atfn main. - A line ending in
;is a statement. The last line of a block with no;, such asOk(Report { words, longest }), is the value the block produces. A block that ends in}(if,for,match) needs no;after it, and nor does astruct. - By convention, types that you or the library define (
Report,String,Result) start with a capital letter, and variables and functions are lowercase.
The program, token by token
The build compiles and runs every form in these tables.
| Mark | You see | It means | Learn more |
|---|---|---|---|
| 1 | // … |
a comment, to the end of the line. # is not a comment in Rust |
|
| 2 | #[derive(Debug)] |
an attribute: a note to the compiler about the next item. This one asks it to write the code that lets {:?} print a Report |
a later lesson |
| 3 | struct Report { words: usize, … } |
a type made of named fields, with the type after the name and a :. i32 is a whole number in 32 bits, u8 one in 8 bits (0 to 255), f64 a decimal number; u means never negative, and usize is the type of lengths. Rust makes you pick |
Structs, Numbers and Types |
| 4 | fn analyze(text: &str) -> Result<Report, String> |
fn defines a function, each parameter is name: Type, and the result type follows ->. & is a reference: access to a value someone else owns, like a pointer that cannot be null. &str is a view of text (what a string literal is), and String is text you own and can grow. Result<Report, String> is either Ok(a Report) or Err(a String): a failure is an ordinary value that is returned, and nothing is thrown |
Functions, Borrowing, Errors |
| 5 | let mut longest = String::new(); |
let creates a variable, and it cannot change unless you add mut. :: reaches a function that belongs to a type or module |
Variables |
| 6 | for word in … { }, text.split_whitespace(), words += 1; |
a loop with no parentheses. . calls a method on a value or reads a field, also through a reference: there is no -> |
Loops, Methods |
| 7 | if cond { } |
the condition needs no parentheses, and the braces are required | If / Else |
| 8 | return Err(…); |
leaves the function now with that value | Functions |
| 9 | Ok(Report { words, longest }) |
no ;, so this is the function’s result. words alone means words: words |
Functions |
| 10 | fn main() -> Result<(), String> |
() is the unit value, which means “nothing”. main returns it on success |
Functions |
| 11 | analyze("…")? |
the ? operator, not a ternary. On Err it returns that error from this function now, converting its type with From when the types differ; on Ok it gives you the value inside |
Errors |
| 12 | println!("{report:?}") |
the ! marks a macro call: it looks like a function call but is expanded by the compiler. {} takes the next argument, {name} takes the variable name, and :? is the debugging format |
Hello World |
| 13 | |s: &str| s.to_uppercase() |
a closure: a small function written inline | Closures |
| 14 | match … { 0 => …, 1..=3 => …, n => … } |
picks the first arm whose pattern (a shape the value is compared to) fits. => separates the pattern from its result. 1..=3 includes 3 and 1..3 stops before it. A bare name such as n matches anything and holds the value |
Match |
What the shapes look like in your language
Pick your language in “Coming from” at the top of the page.
In the loop rows, the & in for w in &words borrows the list. Without it the loop would use words up (Ownership).
The rest of the symbols
The tour covered let mut fn -> ; {} () :: . & ? ! # |x| => ..=. These are the words and symbols it did not use. The next table covers the ones that mean two things.
| Symbol | Meaning | Learn more |
|---|---|---|
trait |
a named set of methods that a type can have: Rust’s interface | Traits |
impl |
impl Square { … } adds methods to a type, outside its struct body. impl Describe for Square { … } gives it a trait’s methods |
Methods, Traits |
self, &self, Self |
in a method: the value it is called on, written as the first parameter (&self borrows it). Self is the type itself |
Methods |
where |
lists, after a signature, what a generic type must be able to do | Generics |
<T> |
a type parameter: a placeholder for a type, as in Vec<i32>, a growable list of i32 |
Generics |
dyn |
a value whose type is not named, only the trait it has. Which method runs is looked up at run time | Traits, then Trait objects |
pub, mod, use |
pub makes an item visible outside its module (the default is private). mod makes a module, a named group of items. use makes a name shorter |
Modules |
as |
converts between simple types, mostly numbers: n as f64. In a use, it renames |
Numbers and Types |
[ ] |
indexes v[0], builds an array [1, 2, 3], and writes the array type [u32; 3] and the slice type &[u32], a borrowed view of a list |
Vectors and Slices |
@ |
in a pattern: n @ 1..=9 matches the range and names the value n |
Patterns |
#![…] |
an attribute about the item that encloses it, usually the whole file | a later lesson |
///, //! |
a doc comment: documentation for the item below it (///) or around it (//!) |
a later lesson |
Symbols that mean two things
| Symbol | Job 1 | Job 2 |
|---|---|---|
& |
a reference: &x borrows without owning, and &mut x may change (Borrowing, Mutable Borrows) |
bit-and: a & b |
* |
follow a reference: *r |
multiply: a * b |
| |
a closure’s parameters: |a, b| … |
bit-or a | b, or “either” in a pattern: 1 | 2 => |
.. |
a range: 0..3 |
“and the rest”, in a struct (Point { x: 9, ..p }) or a pattern ([first, ..]) |
_ |
“any value, I don’t need it” (Match), or a type for the compiler to fill in (Vec<_>) |
a separator inside a number: 1_000_000 |
! |
a macro call: println!. In -> !, “never returns” |
“not”: !done |
' |
names a lifetime ('a, Lifetimes) or a loop ('outer:) |
a character: 'a'. Text uses double quotes |
< |
less-than: 1 < 2 |
the start of type arguments: Vec<i32> |
The last pair has a rule of its own. In a type, Vec<i32> is fine. In an expression, parse<i32>() would look like two comparisons, so Rust asks for :: first: parse::<i32>(). The Rust Reference says the :: “is required before the opening < for generic arguments to avoid ambiguity with the less-than operator” (Paths). It is nicknamed the turbofish. This program runs one line for each job:
#[derive(Debug)]struct Point { x: i32, y: i32,}
fn main() { let n = 6; let r = &n; println!("& as a reference: {r}"); println!("* as dereference: {}, as multiply: {}", *r, *r * 2); let mut count = 1; let r_mut = &mut count; *r_mut += 1; println!("&mut then * changes the original: {count}"); println!("& as bit-and: {}", 0b110 & 0b011);
let or_bits = |a: u8, b: u8| a | b; println!("| in a closure, as bit-or: {}", or_bits(0b100, 0b001)); match n { 1 | 2 => println!("| in a pattern: one or two"), _ => println!("| in a pattern: neither"), }
let p = Point { x: 1, y: 2 }; let q = Point { x: 9, ..p }; let [first, ..] = [10, 20, 30]; println!(".. as a range: {:?}", 0..3); println!(".. in a struct: x={}, y={}", q.x, q.y); println!(".. in a pattern: {first}");
let big = 1_000_000; let letters: Vec<_> = "abc".chars().collect(); println!("_ in a number: {big}, as a type to fill in: {letters:?}");
let done = false; println!("! as not: {}, but also in println!", !done); println!("'a' is a char: {}", 'a'); 'outer: for i in 0..3 { for j in 0..3 { if i + j == 3 { println!("' as a loop label: left both loops at {i},{j}"); break 'outer; } } } println!("< as less-than: {}", 1 < 2); println!("::<> picks a type: {:?}", "5".parse::<i32>());}& as a reference: 6 * as dereference: 6, as multiply: 12 &mut then * changes the original: 2 & as bit-and: 2 | in a closure, as bit-or: 5 | in a pattern: neither .. as a range: 0..3 .. in a struct: x=9, y=2 .. in a pattern: 10 _ in a number: 1000000, as a type to fill in: ['a', 'b', 'c'] ! as not: true, but also in println! 'a' is a char: a ' as a loop label: left both loops at 1,2 < as less-than: true ::<> picks a type: Ok(5)
Going deeper
Paths and dots
:: and . are not interchangeable. :: walks a path to a name: a module, a type, a function or a constant. . works on a value you already hold: a field, or a method called on it. A method call such as s.len() can also be written as a path, with the value passed in:
fn main() { let s = String::from("hello"); println!("s.len(): {}", s.len()); println!("String::len(&s): {}", String::len(&s)); println!("i32::MAX: {}", i32::MAX); println!("std::cmp::max(3, 9): {}", std::cmp::max(3, 9));}s.len(): 5 String::len(&s): 5 i32::MAX: 2147483647 std::cmp::max(3, 9): 9
s.len() and String::len(&s) give the same answer.
Slips in the first hour
The missing mut is the gotcha at the top of the page. Here are other slips that come from habits in other languages. Leaving the ! off a macro:
fn main() { println("hello");}error[E0423]: cannot find function `println` in this scope --> src/main.rs:2:5 | 2 | println("hello"); | ^^^^^^^ not found in this scope | = note: a macro named `println` exists in another namespace help: use `!` to invoke the macro | 2 | println!("hello"); | +
The compiler knows a macro of that name exists and says where to put the !. A one-line if without braces:
fn main() { let n = 3; if n > 1 println!("big");}error: expected `{`, found `println` --> src/main.rs:3:14 | 3 | if n > 1 println!("big"); | ^^^^^^^ expected `{` | note: the `if` expression is missing a block after this condition --> src/main.rs:3:8 | 3 | if n > 1 println!("big"); | ^^^^^ help: you might have meant to write this as part of a block | 3 | if n > 1 { println!("big") }; | + +
This one has no error code. The body of an if or a loop needs braces, even for one statement (a match arm or a closure takes a bare expression). And a field you can read in your own language, but that is private here:
mod shapes { pub struct Square { side: u32, }
impl Square { pub fn new(side: u32) -> Square { Square { side } } }}
fn main() { let sq = shapes::Square::new(4); println!("{}", sq.side);}error[E0616]: field `side` of struct `Square` is private --> src/main.rs:15:23 | 15 | println!("{}", sq.side); | ^^^^ private field
The Rust Reference says that “by default, everything is private” (Visibility and privacy), with two exceptions: trait items and enum variants. Last, ? in a function that returns nothing:
fn main() { let n: i32 = "5".parse()?; println!("{n}");}error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`) --> src/main.rs:2:29 | 1 | fn main() { | --------- this function should return `Result` or `Option` to accept `?` 2 | let n: i32 = "5".parse()?; | ^ cannot use the `?` operator in a function that returns `()` | help: consider adding return type | 1 ~ fn main() -> Result<(), Box<dyn std::error::Error>> { 2 | let n: i32 = "5".parse()?; 3 | println!("{n}"); 4 + Ok(())
E0277. ? needs somewhere to send the failure, so the function’s return type must be one that can hold it: a Result for a Result, an Option for an Option. The help: shows a return type to add. The tour program’s main returns Result<(), String>, which works because its errors are already Strings.
Practice
The first exercise trains reading, not writing: it shows a short program and asks what six calls give.
Read this: what do the calls give?
Do not run the program to find out. Read the match and the ? and fill in each answer. One call lands just past a range, and two fail in different places. An Err holds a String, so write a bare text as Err("oops".to_string()).
// Read this program without running it. Then replace each `todo!()` below with what// the call gives, written as you would write it in code. An `Err` here holds a `String`,// so a bare text needs `.to_string()`: `Err("oops".to_string())`.
fn label(n: i32) -> String { match n { 0 => String::from("zero"), 1..=9 => format!("small {n}"), big @ 10..=99 => format!("big {big}"), _ => String::from("huge"), }}
fn halve(n: i32) -> Result<i32, String> { if n % 2 != 0 { return Err(format!("{n} is odd")); } Ok(n / 2)}
fn quarter(n: i32) -> Result<i32, String> { let half = halve(n)?; halve(half)}
// What each call gives:
/// Hint: which arm does 9 fall in, and what text does that arm build?fn label_of_9() -> &'static str { todo!("which arm does 9 fall in, and what text does that arm build?")}
/// Hint: `big @ 10..=99` names the number it matchedfn label_of_10() -> &'static str { todo!("`big @ 10..=99` names the number it matched")}
/// Hint: is 100 inside `10..=99`? `..=` includes the end, and nothing past itfn label_of_100() -> &'static str { todo!("is 100 inside `10..=99`? `..=` includes the end, and nothing past it")}
/// Hint: both calls to `halve` succeed: 12, then 6, then 3fn quarter_of_12() -> Result<i32, String> { todo!("both calls to `halve` succeed: 12, then 6, then 3")}
/// Hint: the first `halve` works; which number does the second one reject?fn quarter_of_6() -> Result<i32, String> { todo!("the first `halve` works; which number does the second one reject?")}
/// Hint: what does `?` do when `halve(n)` is an `Err`?fn quarter_of_7() -> Result<i32, String> { todo!("what does `?` do when `halve(n)` is an `Err`?")}
fn check<T: PartialEq + std::fmt::Debug>(call: &str, said: T, truth: T) { assert!(said == truth, "misread `{call}`: you said {said:?}"); println!("ok: {call} = {said:?}");}
fn main() { check("label(9)", label_of_9(), label(9).as_str()); check("label(10)", label_of_10(), label(10).as_str()); check("label(100)", label_of_100(), label(100).as_str()); check("quarter(12)", quarter_of_12(), quarter(12)); check("quarter(6)", quarter_of_6(), quarter(6)); check("quarter(7)", quarter_of_7(), quarter(7)); 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
// Read this program without running it. Then replace each `todo!()` below with what// the call gives, written as you would write it in code. An `Err` here holds a `String`,// so a bare text needs `.to_string()`: `Err("oops".to_string())`.
fn label(n: i32) -> String { match n { 0 => String::from("zero"), 1..=9 => format!("small {n}"), big @ 10..=99 => format!("big {big}"), _ => String::from("huge"), }}
fn halve(n: i32) -> Result<i32, String> { if n % 2 != 0 { return Err(format!("{n} is odd")); } Ok(n / 2)}
fn quarter(n: i32) -> Result<i32, String> { let half = halve(n)?; halve(half)}
// What each call gives:
/// Hint: which arm does 9 fall in, and what text does that arm build?fn label_of_9() -> &'static str { "small 9"}
/// Hint: `big @ 10..=99` names the number it matchedfn label_of_10() -> &'static str { "big 10"}
/// Hint: is 100 inside `10..=99`? `..=` includes the end, and nothing past itfn label_of_100() -> &'static str { "huge"}
/// Hint: both calls to `halve` succeed: 12, then 6, then 3fn quarter_of_12() -> Result<i32, String> { Ok(3)}
/// Hint: the first `halve` works; which number does the second one reject?fn quarter_of_6() -> Result<i32, String> { Err("3 is odd".to_string())}
/// Hint: what does `?` do when `halve(n)` is an `Err`?fn quarter_of_7() -> Result<i32, String> { Err("7 is odd".to_string())}
fn check<T: PartialEq + std::fmt::Debug>(call: &str, said: T, truth: T) { assert!(said == truth, "misread `{call}`: you said {said:?}"); println!("ok: {call} = {said:?}");}
fn main() { check("label(9)", label_of_9(), label(9).as_str()); check("label(10)", label_of_10(), label(10).as_str()); check("label(100)", label_of_100(), label(100).as_str()); check("quarter(12)", quarter_of_12(), quarter(12)); check("quarter(6)", quarter_of_6(), quarter(6)); check("quarter(7)", quarter_of_7(), quarter(7)); println!("all checks passed");}ok: label(9) = "small 9"
ok: label(10) = "big 10"
ok: label(100) = "huge"
ok: quarter(12) = Ok(3)
ok: quarter(6) = Err("3 is odd")
ok: quarter(7) = Err("7 is odd")
all checks passedCount to n, including n
Write sum_to(n): the sum of 1 through n, with n counted. Use let mut, a for loop and ..=. The check for n = 1 catches the off-by-one that 1..n gives.
// Return 1 + 2 + ... + n, counting n itself. For n = 0 the answer is 0.fn sum_to(n: u32) -> u32 { todo!("start with `let mut total = 0;`, then `for i in 1..=n` and `total += i`")}
fn main() { assert_eq!(sum_to(4), 10); assert_eq!(sum_to(1), 1); // `1..n` would stop one short and give 0 assert_eq!(sum_to(0), 0); // `1..=0` has nothing in it assert_eq!(sum_to(100), 5050); 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
// Return 1 + 2 + ... + n, counting n itself. For n = 0 the answer is 0.fn sum_to(n: u32) -> u32 { let mut total = 0; for i in 1..=n { total += i; } total}
fn main() { assert_eq!(sum_to(4), 10); assert_eq!(sum_to(1), 1); // `1..n` would stop one short and give 0 assert_eq!(sum_to(0), 0); // `1..=0` has nothing in it assert_eq!(sum_to(100), 5050); println!("all checks passed");}all checks passed
Count the even numbers
Write count_evens for a slice of numbers (&[i32], a borrowed list). Loop over it with for n in nums, and test each n with n % 2 == 0. The checks include an empty slice, zero and negative numbers.
// Count how many numbers in the slice are even. Zero is even.fn count_evens(nums: &[i32]) -> usize { todo!("`let mut count = 0;`, then `for n in nums` and `n % 2 == 0`")}
fn main() { assert_eq!(count_evens(&[1, 2, 3, 4]), 2); assert_eq!(count_evens(&[]), 0); // an empty slice assert_eq!(count_evens(&[0]), 1); // zero is even assert_eq!(count_evens(&[-3, -2, -1]), 1); // negative numbers 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
// Count how many numbers in the slice are even. Zero is even.fn count_evens(nums: &[i32]) -> usize { let mut count = 0; for n in nums { if n % 2 == 0 { count += 1; } } count}
fn main() { assert_eq!(count_evens(&[1, 2, 3, 4]), 2); assert_eq!(count_evens(&[]), 0); // an empty slice assert_eq!(count_evens(&[0]), 1); // zero is even assert_eq!(count_evens(&[-3, -2, -1]), 1); // negative numbers println!("all checks passed");}all checks passed
Name the range
Write age_group, a match with 0 =>, ranges with @, and a bare name for the rest. Each arm builds a String: String::from("baby"), or format!("child ({n})"), which fills in {n} like println! does. The checks include 12 and 13, 19 and 20: the edges where ..= and the next range meet.
// "baby" for 0, "child (n)" for 1 to 12, "teen (n)" for 13 to 19, "adult (n)" for 20 and up.fn age_group(age: u32) -> String { todo!("match age; \"baby\" needs String::from; `n @ 1..=12 =>` keeps the value as n")}
fn main() { assert_eq!(age_group(0), "baby"); assert_eq!(age_group(1), "child (1)"); assert_eq!(age_group(12), "child (12)"); assert_eq!(age_group(13), "teen (13)"); // the edge between two ranges assert_eq!(age_group(19), "teen (19)"); assert_eq!(age_group(20), "adult (20)"); assert_eq!(age_group(99), "adult (99)"); 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
// "baby" for 0, "child (n)" for 1 to 12, "teen (n)" for 13 to 19, "adult (n)" for 20 and up.fn age_group(age: u32) -> String { match age { 0 => String::from("baby"), n @ 1..=12 => format!("child ({n})"), n @ 13..=19 => format!("teen ({n})"), n => format!("adult ({n})"), }}
fn main() { assert_eq!(age_group(0), "baby"); assert_eq!(age_group(1), "child (1)"); assert_eq!(age_group(12), "child (12)"); assert_eq!(age_group(13), "teen (13)"); // the edge between two ranges assert_eq!(age_group(19), "teen (19)"); assert_eq!(age_group(20), "adult (20)"); assert_eq!(age_group(99), "adult (99)"); println!("all checks passed");}all checks passed
Read more
- The Rust Book, Appendix B: Operators and Symbols and Appendix A: Keywords: the complete lists the symbol tables are cut from.
- The Rust Book, Functions, Control Flow and Pattern Syntax (
@,..,|,_). - The Rust Book, Validating References with Lifetimes: what
'adoes and does not do. - The Rust Reference, Tokens, Paths, Comments and Visibility and privacy: the exact rules for each.
- Rust by Example, Formatted print and Expressions.
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.