📜 Mini Book

First we will start with a traditional Hello World:

// This is the main function
fn main() {
    // The statements here will be executed when the compiled binary is called

    // Print text to the console
    println!("Hello World!");
}

As you can see comments starts with //.

fn is the keyword used to declare a functions.

println is a macro. Macros allow us to abstract at a syntactic level, that allows avoid repeated code.

Strings are between ".

Comments

Rust has two kinds of comments: line comments and doc comments.

Line comments are anything after // and extend to the end of the line.

// this is a comment
let x = 5; // this is another comment

The other kind of comment is a doc comment. Doc comments use /// instead of //, and support Markdown notation inside:

/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let five = 5;
///
/// assert_eq!(6, add_one(5));
/// # fn add_one(x: i32) -> i32 {
/// #     x + 1
/// # }
/// ```
fn add_one(x: i32) -> i32 {
    x + 1
}

There is another style of doc comment, //!, to comment containing items. Commonly used inside crates root (lib.rs) or modules root (mod.rs):

//! # The Rust Standard Library
//!
//! The Rust Standard Library provides the essential runtime
//! functionality for building portable Rust software.

Variable Bindings

In many languages, a variable binding would be called a variable, but Rust’s variable bindings have a few tricks up their sleeves. For example the left-hand side of a let expression is a ‘pattern’, not a variable name.

let (x, y) = (1, 2); // x will be one, and y will be two

let z: i32 = 5; // x is a integer from 32 bits

let mut k = 5; // by default are variables a immutable. So to allow mutability use mut

Functions

Every Rust program has at least one function, the main function:

fn main() {
   // here is where the code remains
}

Here’s a complete program that uses a functions called print_number:

fn main() {
    print_number(5); // prints x is: 5
}

fn print_number(x: i32) {
    println!("x is: {}", x);
}

Here’s a complete program that adds two numbers together and prints them:

fn main() {
    print_sum(5, 6); // prints sum is: 11
}

fn print_sum(x: i32, y: i32) {
    println!("sum is: {}", x + y);
}

What about returning a value?

fn add_one(x: i32) -> i32 {
    x + 1
}

fn add_two(x: i32) -> i32 {
    let y: i32 = 2;
    x + y
}

Primitive Types

Booleans

let x = true;
let y: bool = false;

Char

let x = 'x';
let two_hearts = '💕';

Numeric Types

Possible numeric types are i8, i16, i32, i64, u8, u16, u32, u64, isize, usize, f32, f64. Unsigned types use a u for their category, and signed types use i.

let x = 42; // x has type i32
let y = 1.0; // y has type f64
let z:i32 = 64; // z has type i32

Arrays

let a = [1, 2, 3]; // a: [i32; 3]
let mut m = [1, 2, 3]; // m: [i32; 3]

Slice or “view”

let a = [0, 1, 2, 3, 4];
let complete = &a[..]; // A slice containing all of the elements in a
let middle = &a[1..4]; // A slice of a: only the elements 1, 2, and 3

str

Rust’s str type is the most primitive string type. As an unsized type, it’s not very useful by itself, but becomes useful when placed behind a reference, like &str.

let s: &str = "Hello";

Tuples

let x1 = (1, "hello");
let x2: (i32, &str) = (1, "hello");
let tuple = (1, 2, 3);
let x = tuple.0;
let y = tuple.1;
let z = tuple.2;
println!("x is {}", x);

Functions

fn foo(x: i32) -> i32 { x }
let x: fn(i32) -> i32 = foo;

If

Just one choise:

let x = 5;
if x == 5 {
    println!("x is five!");
}

One choise or another:

let x = 5;
if x == 5 {
    println!("x is five!");
} else {
    println!("x is not five :(");
}

Many choises:

let x = 5;
if x == 5 {
    println!("x is five!");
} else if x == 6 {
    println!("x is six!");
} else {
    println!("x is not five or six :(");
}

You can do something like:

let x = 5;
let y = if x == 5 {
    10
} else {
    15
}; // y: i32

Or better:

let x = 5;
let y = if x == 5 { 10 } else { 15 }; // y: i32

Loop

Rust currently provides three approaches to performing some kind of iterative activity: loop, while and for.

Loop

loop {
    println!("Loop forever!");
}

While

let mut x = 5; // mut x: i32
let mut done = false; // mut done: bool
while !done {
    x += x - 3;
    println!("{}", x);
    if x % 5 == 0 {
        done = true;
    }
}

For

The for loop is used to loop a particular number of times:

for x in 0..10 {
    println!("{}", x); // x: i32
}

Rust does not have the “C-style” for loop on purpose. Manually controlling each element of the loop is complicated and error prone, even for experienced C developers.

// Working with ranges
for (i,j) in (5..10).enumerate() {
    println!("i = {} and j = {}", i, j);
}
// Working with iterators
let lines = "line 0\nline 1".lines();
for (linenumber, line) in lines.enumerate() {
    println!("{}: {}", linenumber, line);
}

Ending iteration early

Its possible to end a loop with a guard:

let mut x = 5;
let mut done = false;
while !done {
    x += x - 3;
    println!("{}", x);
    if x % 5 == 0 {
        done = true;
    }
}

But its better to use break:

let mut x = 5;
loop {
    x += x - 3;
    println!("{}", x);
    if x % 5 == 0 { break; }
}

Using continue goes to the next iteration:

for x in 0..10 {
    if x % 2 == 0 { continue; }
    println!("{}", x);
}

Loop labels

'outer: for x in 0..10 {
    'inner: for y in 0..10 {
        if x % 2 == 0 { continue 'outer; } // continues the loop over x
        if y % 2 == 0 { continue 'inner; } // continues the loop over y
        println!("x: {}, y: {}", x, y);
    }
}

Structs

Structs

struct Point {
    x: i32,
    y: i32,
}
fn main() {
    let origin = Point { x: 0, y: 0 }; // origin: Point
    println!("The origin is at ({}, {})", origin.x, origin.y);
}

Update Syntax

A struct can include .. to indicate that you want to use a copy of some other struct for some of the values. For example:

struct Point3d {
    x: i32,
    y: i32,
    z: i32,
}
let mut point = Point3d { x: 0, y: 0, z: 0 };
point = Point3d { y: 1, .. point };
let origin = Point3d { x: 0, y: 0, z: 0 };
let point2 = Point3d { z: 1, x: 2, .. origin };

Tuple structs

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

Unit-like structs

struct Electron;
let x = Electron;

Enums

An enum in Rust is a type that represents data that is one of several possible variants. Each variant in the enum can optionally have data associated with it:

enum Message {
    Quit,
    ChangeColor(i32, i32, i32),
    Move { x: i32, y: i32 },
    Write(String),
}

We use the :: syntax to use the name of each variant: they’re scoped by the name of the enum itself.

let x: Message = Message::Move { x: 3, y: 4 };

enum BoardGameTurn {
    Move { squares: i32 },
    Pass,
}

let y: BoardGameTurn = BoardGameTurn::Move { squares: 1 };

Match

Rust has a keyword, match, that allows you to replace complicated if/else groupings with something more powerful:

let x = 5;

match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    4 => println!("four"),
    5 => println!("five"),
    _ => println!("something else"),
}

match is also an expression, which means we can use it on the right-hand side of a let binding or directly where an expression is used:

let x = 5;

let number = match x {
    1 => "one",
    2 => "two",
    3 => "three",
    4 => "four",
    5 => "five",
    _ => "something else",
};

Match on Enums

enum Message {
    Quit,
    ChangeColor(i32, i32, i32),
    Move { x: i32, y: i32 },
    Write(String),
}

fn quit() { /* ... */ }
fn change_color(r: i32, g: i32, b: i32) { /* ... */ }
fn move_cursor(x: i32, y: i32) { /* ... */ }

fn process_message(msg: Message) {
    match msg {
        Message::Quit => quit(),
        Message::ChangeColor(r, g, b) => change_color(r, g, b),
        Message::Move { x: x, y: y } => move_cursor(x, y),
        Message::Write(s) => println!("{}", s),
    };
}

The Rust compiler checks exhaustiveness, so it demands that you have a match arm for every variant of the enum. If you leave one off, it will give you a compile-time error unless you use _ or provide all possible arms.

Patterns

let x = 1;

match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    _ => println!("anything"),
}

There’s one pitfall with patterns: like anything that introduces a new binding, they introduce shadowing:

let x = 1;
let c = 'c';

match c {
    x => println!("x: {} c: {}", x, c), // here x is the c value and not the x variable
}

println!("x: {}", x)

Multiple Patterns

You can match multiple patterns with |:

let x = 1;
match x {
    1 | 2 => println!("one or two"),
    3 => println!("three"),
    _ => println!("anything"),
}

This prints one or two.

Destructuring

If you have a compound data type, like a struct, you can destructure it inside of a pattern:

struct Point {
    x: i32,
    y: i32,
}
let origin = Point { x: 0, y: 0 };

match origin {
    Point { x, y } => println!("({},{})", x, y),
}

// Use : to give a different name
match origin {
    Point { x: x1, y: y1 } => println!("({},{})", x1, y1),
}

// If don't care about the values
match origin {
    Point { x, .. } => println!("x is {}", x),
}

// .. can match any member
match origin {
    Point { y, .. } => println!("y is {}", y),
}

Ignoring bindings

_ can be used in a pattern to disregard the type and value:

match some_value {
    Ok(value) => println!("got a value: {}", value),
    Err(_) => println!("an error occurred"),
}

_ is valid in any pattern that creates a binding. This can be useful to ignore parts of a larger structure:

fn coordinate() -> (i32, i32, i32) {
    // generate and return some sort of triple tuple
}

let (x, _, z) = coordinate();

.. can be used in a pattern to disregard multiple values:

enum OptionalTuple {
    Value(i32, i32, i32),
    Missing,
}

let x = OptionalTuple::Value(5, -2, 3);

match x {
    OptionalTuple::Value(..) => println!("Got a tuple!"),
    OptionalTuple::Missing => println!("No such luck."),
}

ref and ref mut

The ref keyword is used to get references.

Here, the r inside the match has the type &i32. In other words, the ref keyword creates a reference, for use in the pattern:

let x = 5;

match x {
    ref r => println!("Got a reference to {}", r), // prints Got a reference to 5.
}

If its necessary to use a mutable reference use ref mut:

let mut x = 5;

match x {
    ref mut mr => println!("Got a mutable reference to {}", mr),
}

Ranges

You can match a range of values with …:

let x = 1;

match x {
    1 ... 5 => println!("one through five"),
    _ => println!("anything"),
}

For chars use like:

let x = '🚀';

match x {
    'a' ... 'j' => println!("early letter"),
    'k' ... 'z' => println!("late letter"),
    _ => println!("something else"),
}

Bindings

Values are linked to names with @:

let x = 3;

match x {
    e @ 1 ... 5 => println!("got a range element {}", e),
    e @ 6 ... 10 => println!("got a range element {}", e),
    _ => println!("anything"),
}

This is useful when you want to do a complicated match of part of a data structure:

#[derive(Debug)]
struct Person {
    name: Option<String>,
}

let name = "Steve".to_string();
let x: Option<Person> = Some(Person { name: Some(name) });
match x {
    Some(Person { name: ref a @ Some(_), .. }) => println!("{:?}", a),
    _ => {}
}

If you use @ with |, you need to make sure the name is bound in each part of the pattern:

let x = 5;

match x {
    e @ 1 ... 5 | e @ 8 ... 10 => println!("got a range element {}", e),
    _ => println!("anything"),
}

Guards

You can introduce ‘match guards’ with if:

enum OptionalInt {
    Value(i32),
    Missing,
}

let x = OptionalInt::Value(5);

match x {
    OptionalInt::Value(i) if i > 5 => println!("Got an int bigger than five!"),
    OptionalInt::Value(..) => println!("Got an int!"),
    OptionalInt::Missing => println!("No such luck."),
}

If you’re using if with multiple patterns, the if applies to both sides:

let x = 4;
let y = false;

match x {
    4 | 5 if y => println!("yes"),
    _ => println!("no"),
}

Using Patterns

Mix

There are many ways to match patterns and it’s possible even mix then:

match x {
    Foo { x: Some(ref name), y: None } => ...
}

Expression and statement-oriented

Unlike many languages that offer pattern matching, Rust embraces both statement- and expression-oriented programming.

This function maps a non-negative integer to a string rendering it as an ordinal (“1st”, “2nd”, “3rd”, …):

fn num_to_ordinal_expr(x: u32) -> String {
    format!("{}{}", x, match (x % 10, x % 100) {
        (1, 1) | (1, 21...91) => "st",
        (2, 2) | (2, 22...92) => "nd",
        (3, 3) | (3, 23...93) => "rd",
        _                     => "th"
    })
}

Syntax

Rust provides a nice way to way multiple chain calls. Consider the follow code:

baz(bar(foo));

You can do the same using fluent interface:

foo.bar().baz();

Calls

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl Circle {
    fn reference(&self) {
       println!("taking self by reference!");
    }

    fn mutable_reference(&mut self) {
       println!("taking self by mutable reference!");
    }

    fn takes_ownership(self) {
       println!("taking ownership of self!");
    }
}

Chaining Methods

This example shows how chaining methods (fluent interface).

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }

    fn grow(&self, increment: f64) -> Circle {
        Circle { x: self.x, y: self.y, radius: self.radius + increment }
    }
}

fn main() {
    let c = Circle { x: 0.0, y: 0.0, radius: 2.0 };
    println!("{}", c.area());

    let d = c.grow(2.0).area();
    println!("{}", d);
}

Associated Function

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl Circle {
    fn new(x: f64, y: f64, radius: f64) -> Circle {
        Circle {
            x: x,
            y: y,
            radius: radius,
        }
    }
}

fn main() {
    let c = Circle::new(0.0, 0.0, 2.0);
}

Builder Pattern

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

struct CircleBuilder {
    x: f64,
    y: f64,
    radius: f64,
}

impl CircleBuilder {
    fn new() -> CircleBuilder {
        CircleBuilder { x: 0.0, y: 0.0, radius: 1.0, }
    }

    fn x(&mut self, coordinate: f64) -> &mut CircleBuilder {
        self.x = coordinate;
        self
    }

    fn y(&mut self, coordinate: f64) -> &mut CircleBuilder {
        self.y = coordinate;
        self
    }

    fn radius(&mut self, radius: f64) -> &mut CircleBuilder {
        self.radius = radius;
        self
    }

    fn finalize(&self) -> Circle {
        Circle { x: self.x, y: self.y, radius: self.radius }
    }
}

fn main() {
    let c = CircleBuilder::new()
                .x(1.0)
                .y(2.0)
                .radius(2.0)
                .finalize();

    println!("area: {}", c.area());
    println!("x: {}", c.x);
    println!("y: {}", c.y);
}