Ferrugo

From a function named main to the borrow that lets a value stay.

Thirteen short programs on this page. Each one has a check you answer here. Try it opens the official playground with that program, and only when you ask. Traits, threads, and async are on the next page.

A small program

The program starts at main

An executable Rust program begins in a function named main. The body is a block. This one prints a single line and then ends.

fn main() {
    println!("The bridge holds.");
}

What to notice. fn names a function. The empty parentheses mean main takes no arguments. The programs on this page are Rust 2024.

Where does this program start?
What line does the program write?
Try it

println! fills a placeholder

println! is a macro, which is why the name ends in !. Braces in the string are a placeholder. The matching name is filled in when the line runs.

fn main() {
    let span = 12;
    println!("Span: {span} meters");
}

What to notice. The braces are not printed. {span} is replaced by the value of the binding span.

What is printed?
Why does println! end with an exclamation mark?
Try it

The Cargo shape

A real package is a directory. Cargo.toml names it. src/main.rs holds the program. Cargo reads that layout when you build.

// ferrugo-bridge/
// ├── Cargo.toml
// └── src/
//     └── main.rs

fn main() {
    println!("cargo run");
}

What to notice. Cargo.toml is the manifest. src/main.rs is the binary crate root. Comments with // are not executed.

This site does not run Cargo. Try it opens the program that would live in src/main.rs. On your own machine, cargo run builds and runs the package.

Which file holds fn main?
On your own machine, which command builds and runs this package?
Try it

Syntax

let and mut

let introduces a binding. It cannot be assigned again unless you write mut. The value can still be read either way.

fn main() {
    let bolts = 4;
    let mut coats = 1;
    coats += 1;
    println!("{bolts} bolts, {coats} coats");
}

What to notice. bolts is immutable. coats is mutable, so coats += 1 is allowed and coats becomes 2.

What is printed?
What happens if you add the line bolts = 5?
Try it

A few types

A binding can name its type. i32 is a 32-bit integer. bool is true or false. char is one Unicode scalar, written with single quotes. &str is a borrowed view of text, written with double quotes.

fn main() {
    let piers: i32 = 2;
    let open: bool = true;
    let mark: char = 'F';
    let name: &str = "Ferrugo";
    println!("{piers} {open} {mark} {name}");
}

What to notice. The compiler can often infer the type. Writing it out makes the four shapes easy to see.

What is printed?
What is &str in this program?
Try it

Functions return a value

A function names its parameters and, after ->, the type it returns. The last expression in the body is the return value when it has no semicolon.

fn span(piers: i32) -> i32 {
    piers * 6
}

fn main() {
    println!("{}", span(2));
}

What to notice. piers * 6 has no semicolon, so it is the returned i32. A semicolon would turn it into a statement and the function would not return that number.

What is printed?
Why is there no semicolon after piers * 6?
Try it

if and loops

if runs a block when the condition is true. for walks a range. 1..=2 includes both ends, so the body runs for 1 and for 2.

fn main() {
    let rust = 3;
    if rust > 2 {
        println!("coat");
    }
    for pier in 1..=2 {
        println!("pier {pier}");
    }
}

What to notice. The if has no parentheses around the condition. The range 1..=2 is inclusive. 1..2 would stop before 2.

What is the first line printed?
How many lines start with pier?
Try it

Data

Structs and methods

A struct groups named fields. impl adds methods. &self borrows the value, so calling the method does not move the struct away.

struct Bridge {
    span: i32,
}

impl Bridge {
    fn label(&self) -> String {
        format!("{}m", self.span)
    }
}

fn main() {
    let bridge = Bridge { span: 12 };
    println!("{}", bridge.label());
}

What to notice. label returns an owned String built by format!. self.span reads the field through the borrow.

What is printed?
What does &self mean on label?
Try it

Enums and match

An enum is a value that is one of a fixed set of variants. match must cover every variant, so a forgotten case is a compile error rather than a surprise at runtime.

enum Coat {
    Sound,
    Flaking,
}

fn report(coat: Coat) -> &'static str {
    match coat {
        Coat::Sound => "sound",
        Coat::Flaking => "flaking",
    }
}

fn main() {
    println!("{}", report(Coat::Flaking));
}

What to notice. Each arm is a pattern and an expression. Coat::Flaking selects the second arm.

What is printed?
What happens if the Sound arm is deleted?
Try it

Vec and String

Vec is a growable list. String is growable owned text. push adds one item to the vector. push_str appends text to the String.

fn main() {
    let mut names = vec!["north", "south"];
    names.push("center");
    let mut title = String::from("span");
    title.push_str("s");
    println!("{} {}", names.len(), title);
}

What to notice. vec! builds a Vec. The items here are &str. String::from owns the characters, so the String can grow.

What is printed?
Why is title a String instead of a &str?
Try it

The Rust idea

Ownership and moves

A String has one owner. Assigning it to another binding moves it. The first binding cannot be used after the move. Integers are Copy, so they are duplicated instead. A String is not.

fn main() {
    let name = String::from("Ferrugo");
    let moved = name;
    println!("{moved}");
}

What to notice. After the move, moved owns the text. name is no longer valid. That is why the print uses moved.

What is printed?
What happens if you also print name after the move?
Try it

References and borrowing

A reference lets a function use a value without taking ownership. &name lends the String. When length returns, name is still the owner, so main can print it.

fn length(name: &str) -> usize {
    name.len()
}

fn main() {
    let name = String::from("Ferrugo");
    let count = length(&name);
    println!("{name} is {count}");
}

What to notice. &name creates a shared borrow. The parameter type &str accepts that borrow. len() counts bytes, and these seven letters are one byte each.

What is printed?
Who owns the String after length returns?
Try it

Option, Result, and ?

Option is either Some(value) or None. Result is either Ok(value) or Err(error). The ? operator, used in a function that returns Result, returns the error early so the rest of the function runs only on Ok.

fn span_from(text: &str) -> Result<i32, std::num::ParseIntError> {
    let meters = text.parse::<i32>()?;
    Ok(meters)
}

fn main() {
    let meters = span_from("12").expect("span");
    println!("{meters}");
}

What to notice. parse returns a Result. ? unwraps the Ok or leaves the function with the Err. expect is for main, which does not return that error type. None is the Option shape of absence. Err is the Result shape of failure.

What does this program print?
What does ? do when the text is not an integer?
Try it

The next span

The short path ends at Result. Traits, generics, lifetimes, modules, iterators, threads, and async each have their own program on the next page.

Continue