Introduction

After reading The Book, I tried to take as many notes as possible to condense into a high level overview. My idea is that before you read The Book, you could skim through this website super quick and pickup the high level concepts before diving into anything deeper. Ideally, after reading through this, you will not get stuck in a lot of the parts most people do when learning Rust.

Best of luck and happy reading!

Rusty Cloud

If you are reading this then you are already aware what the Rust programming language is and maybe have even heard a couple things about it. Some of those things are "It's fast", "It is the hot new language", "It is a memory safe language". You probably also heard that "It has a steep learning curve". While this may be true, I think a couple things work in the favor of Rust to reduce that curve quite heavily.

Rust focuses on 3 main pillars:

  • Performance
    • Rust is blazingly fast and efficient
  • Reliability
    • Code can finally be "done". Many "crates" haven't been touched in years, but still get used and pulled on a daily basis
  • Productivity
    • Rust has tons of documentation with great compile time error messages that prevent common in production mistakes

One thing is, if you are coming from a language without a Garbage Collector, and have had to manually allocate and clean memory, then you shouldn't have too difficult of a time picking up the quirks of Rust. If you are like me, you are coming from Python, Java/Scala, Go, or some other garbage collected language where we have never had to think about memory allocation. Essentially, as variables, functions, data structures build up in a program, garbage collected languages will monitor whether it needs to discard no longer used memory. Here is a quick example comparing python to Rust.

## Assign `test` to `x`
x = "test"

## Assign `x`'s value to `y`
y = x

## Print the value of x (should get "test")
print(x)

## Change the value of `x` to `another`
x = "another"

## Print the value of `x` (should be `another`)
print(x)

## Print the value of `y` (should be `test`)
print(y)
output:
"test"
"another"
"test"

As we can see y equals the value of x at the time x's value was assigned to y. Let's try to run something similar in Rust...

Hit the play button in the code box

fn main() {
    // initiate `x` with a String value `test`
    let x = String::from("test");
    // initiate `y` with the value of `x`
    let y = x;

    // print the values
    println!("{x}");
    println!("{y}");
}

We get an error talking about moving values and borrowing. Don't worry about what they mean right now, just know that Rust can seem strange at first. We will dive into this much more and is part of the foundation of the Rust language.

Another thing working in reducing the Learning curve's favor is the amazing amount of resources and community that is availabe to you. I personally recommend reading "The Book" first, but here is a list of good resources:

Fundamentals

This will cover all of those general knowledge programming concepts you need before you start writing in any language. Some of these will be very familiar because the concepts are present in other languages (e.g. if-else statements, adding types to variables, creating functions), but some will be uniqure to Rust (e.g. Borrowing and Shadowing).

Feel free to skim this section if you are coming from other compiled languages, but I highly recommend not skipping it as there may be some Rust quirks buried in some of the simpler concepts.

Variables

Variables and Mutability

By default, variables are immutable in Rust. This means that once a value is bound to a variable name, you are no longer able to make changes to it. For example, the following will give you an error:

fn main() {
	// Let's assign the value of 5 to `x`
	let x = 5;
	println!("The value of x is: {x}");
	
	// Let's try reassigning `x` to a value of 6
	x = 6;
	println!("The value of x is: {x}");
}

These copiler errors can seem burdensome and frustrating at first, but you will soon grow to love the rich and detailed feedback of the Rust compilier. Rust's compiler even tells you how to correct the error in most instances. By reading the output of the compiler, we can see that we need to add a mut identifier to our variable of x. mut will turn our variable into a mutable instance. That means we can make changes to it, but this can also be dangerous. Which is why it is has to be specified, so you are aware that you are doing something dangerous (this is a recurring theme in Rust). Let now change our x variable to be mutable :

fn main() {
	// Let's assign the value of 5 to `x`
	let mut x = 5;
	println!("The value of x is: {x}");
	
	// Let's try reassigning `x` to a value of 6
	x = 6;
	println!("The value of x is: {x}");
}

Constants

Like immutable variables, constants are values that are bound to a name and are not allowed to change, but there are a few differences between constants and variables. You are not allowed to use mut with constants. You also use const keyword when declaring a constant and you need to annotate the type (i.e. if my constanct is an integer I need to put : u8 after the name of the constant). Constants can be delared in any scope, they also need to be set and cannot be the result of a runtime expression. Rust's standard naming convention for constants is all uppercase with an underscore between words.

fn main() {
	let FOUR_HOURS_IN_SECONDS = 4 * 60 * 60;
	
	println!("{FOUR_HOURS_IN_SECONDS}");
}

Shadowing

You can declare a new variable with the same name as a previous variable, you would then say the first variable is shadowed by the second. the second variable overshadows the first, taking any uses of the variable name to itself until either it itself is shadowed or the scope ends. Here is an example:

fn main() {
	// Assign 5 to `x`
	let x = 5;
	
	// Reassign the variable of `x`
	let x = x + 1;
	
	{
		// Assign `x` a new value to this inner scope
		let x = x * 2;
		
		println!("The value of x in the inner scope is: {x}");
	}
	
	println!("The value of x in the outer scope is: {x}");
}

You might be curious why this ran but the previous code gave an error when we tried to reassign x. Look close at the usage of let in both code blocks. Shadowing is different from marking a variable as mut because we’ll get a compile-time error if we accidentally try to reassign to this variable without using the let keyword. By using let, we can perform a few transformations on a value but have the variable be immutable after those transformations have been completed. The other difference between mut and shadowing is that because we’re effectively creating a new variable when we use the let keyword again, we can change the type of the value but reuse the same name.

Let's test this out in practice. We are going to initialize spaces as a string type then reassign it as a integer type:

fn main() {
	let spaces = "     ";
	let spaces = spaces.len();
}

We don't get an error because we reinitialzied with let. Now, let's try it without let and read the compiler error:

fn main() {
	let mut spaces = "     ";
	spaces = spaces.len();
}

Even though spaces is mutable, we tried to assign it a different type, which throws a compiler errror.

Exercises

Exercise 1

fn main() {
    x = 5;
    println!("x has the value {}", x);
}
Solution
fn main() {
    let x = 5; // You needed to add a `let` in front of the variable `x`
    println!("x has the value {}", x);
}

Exercise 2

fn main() {
    let x;
    if x == 10 {
        println!("x is ten!");
    } else {
        println!("x is not ten!");
    }
}
Solution
fn main() {
    let x: u8 = 10; // Make sure you intiailize `x`
    if x == 10 {
        println!("x is ten!");
    } else {
        println!("x is not ten!");
    }
}

Exercise 3

fn main() {
    let x: i32;
    println!("Number {}", x);
}
Solution
fn main() {
    let x: i32 = 0; // Make sure you initialize `x`
    println!("Number {}", x);
}

Exercise 4

fn main() {
    let x = 3;
    println!("Number {}", x);
    x = 5; // don't change this line
    println!("Number {}", x);
}
Solution
fn main() {
    let mut x = 3; // add the `mut` to make the variable mutable to change it to 5
    println!("Number {}", x);
    x = 5; // don't change this line
    println!("Number {}", x);
}

Exercise 5

fn main() {
    let number = "T-H-R-E-E"; // don't change this line
    println!("Spell a Number : {}", number);
    number = 3; // don't rename this variable
    println!("Number plus two is : {}", number + 2);
}
Solution
fn main() {
    let number = "T-H-R-E-E"; // don't change this line
    println!("Spell a Number : {}", number);
	// Change `number` to a different scope
    {
        let number: u8 = 3; // don't rename this variable
        println!("Number plus two is : {}", number + 2);
    }
}

Exercise 6

const NUMBER = 3;
fn main() {
    println!("Number {}", NUMBER);
}
Solution
const NUMBER: u8 = 3;
fn main() {
    println!("Number {}", NUMBER);
}

Data Types

Data Types tell Rust which can of data is being specified so it knows how to work with that data. Rust has two major subsets of data types: scalar and compound.

Rust is a statically typed language, which means it needs to know the types of all variables at compile time.

Scalar Types

A scalar type represents a single value. Rust has four primary scalar types: integers, floating-point numbers, Booleans, and characters.

Integer

An integer is a number without a fractional component. E.g. 1, 2, 3, 4, 5... You can use the prefix u for unsigned integer types (i.e. non-negative numbers don't need a sign -). Or you can use i for integers that could potentially be negative.

LengthSignedUnsigned
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize

Signed and unsigned refer to whether it’s possible for the number to be negative—in other words, whether the number needs to have a sign with it (signed) or whether it will only ever be positive and can therefore be represented without a sign (unsigned). Each signed variant can store numbers from –(2n – 1) to 2n – 1 – 1 inclusive, where n is the number of bits that variant uses. So an i8 can store numbers from –(27) to 27 – 1, which equals –128 to 127. Unsigned variants can store numbers from 0 to 2n – 1, so a u8 can store numbers from 0 to 28 – 1, which equals 0 to 255.

Number literalsExample
Decimal98_222
Hex0xff
Octal0o77
Binary0b1111_0000
Byte (u8 only)b'A'

So how do you know which type of integer to use? If you’re unsure, Rust’s defaults are generally good places to start: integer types default to i32.

Floating-Point Types

Rust also has two primitive types for floating-point numbers, which are numbers with decimal points. Rust’s floating-point types are f32 and f64, which are 32 bits and 64 bits in size, respectively. The default type is f64 because on modern CPUs, it’s roughly the same speed as f32 but is capable of more precision. All floating-point types are signed.

fn main() {
	let x: f32 = 4.234;
}

The Boolean Type

As in most other programming languages, a Boolean type in Rust has two possible values: true and false. Booleans are one byte in size.

fn main() {
	let t = true;
	
	let f: bool = false;
}

The Character Type

Rust’s char type is the language’s most primitive alphabetic type.

fn main() {
	let a = 'b';
	let z: char = 'Z';
}

Note that we specify char literals with single quotes, as opposed to string literals, which use double quotes.

Compund Types

Compound types can group multiple values into one type. Rust has two primitive compound types: tuples and arrays.

The Tuple Type

A tuple is a general way of grouping together a number of values with a variety of types into one compound type. Tuples have a fixed length: once declared, they cannot grow or shrink in size. We create a tuple by writing a comma-separated list of values inside parentheses.

fn main() {
	let tup: (i32, f64, u8) = (1000, 3.14159, 7);
	
	let (a, b, c) = tup;
	
	println!("The value of b is: {b}");
}

It then uses a pattern with let to take tup and turn it into three separate variables, a, b, and c. This is called destructuring because it breaks the single tuple into three parts.

We can also access a tuple element directly by using a period (.) followed by the index of the value we want to access.

fn main() {
	let tup: (i32, f64, u8) = (1000, 3.14159, 7);
	
	let one_thousand = tup.0;
	let text_pi = tup.1;
	let lucky_number_seven = tup.2;
	
	println!("{text_pi}");
}

The tuple without any values has a special name, unit.

The Array Type

Another way to have a collection of multiple values is with an array. Unlike a tuple, every element of an array must have the same type. Unlike arrays in some other languages, arrays in Rust have a fixed length.

fn main() {
	let a  = [1, 2, 3, 4, 5];
	println!("{:?}", a);
}

Arrays are useful when you want your data allocated on the stack rather than the heap or when you want to ensure you always have a fixed number of elements. An array isn’t as flexible as the vector type, though. A vector is a similar collection type provided by the standard library that is allowed to grow or shrink in size. If you’re unsure whether to use an array or a vector, chances are you should use a vector. However, arrays are more useful when you know the number of elements will not need to change.

You write an array’s type using square brackets with the type of each element, a semicolon, and then the number of elements in the array.

fn main() {
	let a: [i32; 5] = [1, 2, 3, 4, 5];
	println!("{:?}", a)
}

You can also initialize an array to contain the same value for each element by specifying the initial value, followed by a semicolon, and then the length of the array in square brackets.

fn main() {
	let a = [5; 10];
	
	println!("{:?}", a);
}

An array is a single chunk of memory of a known, fixed size that can be allocated on the stack. You can access elements of an array using indexing,

fn main() {
	let a = [1, 2, 3, 4, 5];
	
	let first = a[0];
	let second = a[1];
	
	println!("{first} then {second}");
}

Let’s see what happens if you try to access an element of an array that is past the end of the array.

fn main() {
	let a = [1, 2, 3, 4, 5];
	
	let err = a[8];
}

The program resulted in a runtime error at the point of using an invalid value in the indexing operation.

Exercises

Exercise 1

fn main() {
    // Booleans (`bool`)

    let is_morning = true;
    if is_morning {
        println!("Good morning!");
    }

    let // Finish the rest of this line like the example! Or make it be false!
    if is_evening {
        println!("Good evening!");
    }
}
Solution
fn main() {
    // Booleans (`bool`)
    let is_morning = true;
    if is_morning {
        println!("Good morning!");
    }
//
    let is_evening = true; // Finish the rest of this line like the example! Or make it be false!
    if is_evening {
        println!("Good evening!");
    }
}

Exercise 2

fn main() {
    // Characters (`char`)

    // Note the _single_ quotes, these are different from the double quotes
    // you've been seeing around.
    let my_first_initial = 'C';
    if my_first_initial.is_alphabetic() {
        println!("Alphabetical!");
    } else if my_first_initial.is_numeric() {
        println!("Numerical!");
    } else {
        println!("Neither alphabetic nor numeric!");
    }

    let // Finish this line like the example! What's your favorite character?
    // Try a letter, try a number, try a special character, try a character
    // from a different language than your own, try an emoji!
    if your_character.is_alphabetic() {
        println!("Alphabetical!");
    } else if your_character.is_numeric() {
        println!("Numerical!");
    } else {
        println!("Neither alphabetic nor numeric!");
    }
}
Solution

fn main() {
    // Characters (`char`)
    // Note the _single_ quotes, these are different from the double quotes
    // you've been seeing around.
    let my_first_initial = 'C';
    if my_first_initial.is_alphabetic() {
        println!("Alphabetical!");
    } else if my_first_initial.is_numeric() {
        println!("Numerical!");
    } else {
        println!("Neither alphabetic nor numeric!");
    }
//
    let your_character = 'A'; // Finish this line like the example! What's your favorite character?
    // Try a letter, try a number, try a special character, try a character
    // from a different language than your own, try an emoji!
    if your_character.is_alphabetic() {
        println!("Alphabetical!");
    } else if your_character.is_numeric() {
        println!("Numerical!");
    } else {
        println!("Neither alphabetic nor numeric!");
    }
}

Exercise 3

fn main() {
    let a = ???

    if a.len() >= 100 {
        println!("Wow, that's a big array!");
    } else {
        println!("Meh, I eat arrays like that for breakfast.");
        panic!("Array not big enough, more elements needed")
    }
}
Solution
fn main() {
    let a = [0; 100];
//
    if a.len() >= 100 {
        println!("Wow, that's a big array!");
    } else {
        println!("Meh, I eat arrays like that for breakfast.");
    }
}

Exercise 4

#![allow(unused)]
fn main() {
#[test]
fn slice_out_of_array() {
    let a = [1, 2, 3, 4, 5];

    let nice_slice = ???

    assert_eq!([2, 3, 4], nice_slice)
}
}
Solution
#![allow(unused)]
fn main() {
#[test]
fn slice_out_of_array() {
    let a = [1, 2, 3, 4, 5];
//
    let nice_slice = &a[1..4];
//
    assert_eq!([2, 3, 4], nice_slice)
}
}

Exercise 5

fn main() {
    let cat = ("Furry McFurson", 3.5);
    let /* your pattern here */ = cat;

    println!("{} is {} years old.", name, age);
}
Solution
fn main() {
    let cat = ("Furry McFurson", 3.5);
    let (name, age)/* your pattern here */ = cat;
//
    println!("{} is {} years old.", name, age);
}

Exercise 6

#![allow(unused)]
fn main() {
#[test]
fn indexing_tuple() {
    let numbers = (1, 2, 3);
    // Replace below ??? with the tuple indexing syntax.
    let second = ???;

    assert_eq!(2, second,
        "This is not the 2nd number in the tuple!")
}
}
Solution
#![allow(unused)]
fn main() {
#[test]
fn indexing_tuple() {
    let numbers = (1, 2, 3);
    // Replace below ??? with the tuple indexing syntax.
    let second = numbers.1;
//
    assert_eq!(2, second,
        "This is not the 2nd number in the tuple!")
}
}

Functions

You are already familiar with functions, becuase in previous examples we have seen the use of the fn main() {} function. The main function is the entrypoint to many programs. You use fn to declare new functions. Rust code uses snake case as the conventional style for function and variable names, in which all letters are lowercase and underscores separate words.

fn main() {
    println!("Hello, World");

    another_function();
}

fn another_function() {
    println!("another function");
}

Parameters

We can define functions to have parameters, which are special variables that are part of a function’s signature. When a function has parameters, you can provide it with concrete values for those parameters. Technically, the concrete values are called arguments, but in casual conversation, people tend to use the words parameter and argument interchangeably for either the variables in a function’s definition or the concrete values passed in when you call a function.

fn main() {
    another_function(5);
}

fn another_function(x: i32) {
    println!("The value of x is: {x}");
}

In function signatures, you must declare the type of each parameter. This is a deliberate decision in Rust’s design: requiring type annotations in function definitions means the compiler almost never needs you to use them elsewhere in the code to figure out what type you mean.

fn main() {
    print_labeled_measurement(5, 'h');
}

fn print_labeled_measurement(value: i32, unit_label: char) {
    println!("The measurement is: {value}{unit_label}");
}

Statments and Expressions

Function bodies are made up of a series of statements optionally ending in an expression. So far, the functions we’ve covered haven’t included an ending expression, but you have seen an expression as part of a statement. Because Rust is an expression-based language, this is an important distinction to understand. Other languages don’t have the same distinctions, so let’s look at what statements and expressions are and how their differences affect the bodies of functions.

  • Statements are instructions that perform some action and do not return a value.
  • Expressions evaluate to a resultant value.

Function definitions are also statements and staements don't return values. Expressions evaluate to a value and make up most of the rest of the code that you’ll write in Rust.

  • Calling a function is an expression.
  • Calling a macro is an expression.

Expressions do not include ending semicolons. If you add a semicolon to the end of an expression, you turn it into a statement, and it will then not return a value. Keep this in mind as you explore function return values and expressions next.

Functions with Return Values

Functions can return values to the code that calls them. We don’t name return values, but we must declare their type after an arrow (->). In Rust, the return value of the function is synonymous with the value of the final expression in the block of the body of a function. You can return early from a function by using the return keyword and specifying a value, but most functions return the last expression implicitly.

fn five() -> i32 {
    5
}

fn main() {
    let x = five();

    println!("The value of x is: {x}", x)
}

There are no function calls, macros, or even let statements in the five function—just the number 5 by itself. That’s a perfectly valid function in Rust.

The five function has no parameters and defines the type of the return value, but the body of the function is a lonely 5 with no semicolon because it’s an expression whose value we want to return.

Exercises

Control Flow

The ability to run some code depending on whether a condition is true and to run some code repeatedly while a condition is true are basic building blocks in most programming languages. The most common constructs that let you control the flow of execution of Rust code are if expressions and loops.

if Expressions

An if expression allows you to branch your code depending on conditions. All if expressions start with the keyword if, followed by a condition.

fn main() {
    let x = 1;

    if x > 5 {
        println!("x is greater than five and has a value of: {x}");
    } else {
        println!("x is less than five and has a value of: {x}");
    }
}

Unlike languages such as Ruby and JavaScript, Rust will not automatically try to convert non-Boolean types to a Boolean.

Handling Multiple Conditions with else if

You can use multiple conditions by combining if and else in an else if expression.

fn main() {
    let x = 10;

    if x % 4 == 0 {
        println!("{x} is divisible by 4");
    } else if x % 3 == 0{
        println!("{x} is divisible by 3");
    } else {
        println!("{x} is not divisible by 4 or 3");
    }
}

Using too many else if expressions can clutter your code, so if you have more than one, you might want to refactor your code.

Using an if let

Because if is an expression, we can use it on the right side of a let statement to assign the outcome to a variable.

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };

    println!("The value of the number is: {number}");
}

Remember that blocks of code evaluate to the last expression in them, and numbers by themselves are also expressions. In this case, the value of the whole if expression depends on which block of code executes. This means the values that have the potential to be results from each arm of the if must be the same type.

Repition with Loops

It’s often useful to execute a block of code more than once. For this task, Rust provides several loops, which will run through the code inside the loop body to the end and then start immediately back at the beginning.

The following code will just give a playground timeout error, if you run this on your own machine, the loop will continue forever. Thanks for not breaking my server!

fn main() {
    loop {
        println!("Too infinity, and beyond!");
    }
}

Rust also provides a way to break out of a loop using code. You can place the break keyword within the loop to tell the program when to stop executing the loop. We can use continue, which in a loop tells the program to skip over any remaining code in this iteration of the loop and go to the next iteration.

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {result}");
}

Before the loop, we declare a variable named counter and initialize it to 0. Then we declare a variable named result to hold the value returned from the loop. On every iteration of the loop, we add 1 to the counter variable, and then check whether the counter is equal to 10. When it is, we use the break keyword with the value counter * 2. After the loop, we use a semicolon to end the statement that assigns the value to result.

Loop labes to Disambiguate Between Multiple Loops

If you have loops within loops, break and continue apply to the innermost loop at that point. You can optionally specify a loop label on a loop that you can then use with break or continue to specify that those keywords apply to the labeled loop instead of the innermost loop. Loop labels must begin with a single quote.

fn main() {
    let mut count = 0;
    'counting_up: loop {
        println!("count = {count}");
        let mut remaining = 10;

        loop {
            println!("remaining = {remaining}");
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {count}");
}

Conditional Loops with while

A program will often need to evaluate a condition within a loop. While the condition is true, the loop runs. When the condition ceases to be true, the program calls break, stopping the loop. It’s possible to implement behavior like this using a combination of loop, if, else, and break;

fn main() {
    let mut x = 3; 

    while x != 0 {
        println!("{x}");

        x -= 1;
    }

    println!("LIFTOFF!");
}

This construct eliminates a lot of nesting that would be necessary if you used loop, if, else, and break, and it’s clearer. While a condition evaluates to true, the code runs; otherwise, it exits the loop.

Looping Through a collection with for

You can choose to use the while construct to loop over the elements of a collection, such as an array.

fn main() {
    let a = [10, 20, 30, 40, 50];
    let mut index = 0;

    while index < 5 {
        println!("The value is: {}", a[index]);

        index += 1;
    }
}

However, this approach is error prone; we could cause the program to panic if the index value or test condition is incorrect. For example, if you changed the definition of the a array to have four elements but forgot to update the condition to while index < 4.

As a more concise alternative, you can use a for loop and execute some code for each item in a collection.

fn main() {
    let a = [10, 20, 30, 40, 50];

    for i in a {
        println!("The value is: {i}");
    }
}

The safety and conciseness of for loops make them the most commonly used loop construct in Rust.

Exercises

Ownership

Ownership is Rust’s most unique feature and has deep implications for the rest of the language. It enables Rust to make memory safety guarantees without needing a garbage collector, so it’s important to understand how ownership works.

Before you get started please try to understand the following concepts of what the STACK and HEAP are in reference to computer memory!

The Stack and the Heap

Many programming languages don’t require you to think about the stack and the heap very often. But in a systems programming language like Rust, whether a value is on the stack or the heap affects how the language behaves and why you have to make certain decisions. Parts of ownership will be described in relation to the stack and the heap later in this chapter, so here is a brief explanation in preparation.

Both the stack and the heap are parts of memory available to your code to use at runtime, but they are structured in different ways. The stack stores values in the order it gets them and removes the values in the opposite order. This is referred to as last in, first out. Think of a stack of plates: when you add more plates, you put them on top of the pile, and when you need a plate, you take one off the top. Adding or removing plates from the middle or bottom wouldn’t work as well! Adding data is called pushing onto the stack, and removing data is called popping off the stack. All data stored on the stack must have a known, fixed size. Data with an unknown size at compile time or a size that might change must be stored on the heap instead.

The heap is less organized: when you put data on the heap, you request a certain amount of space. The memory allocator finds an empty spot in the heap that is big enough, marks it as being in use, and returns a pointer, which is the address of that location. This process is called allocating on the heap and is sometimes abbreviated as just allocating (pushing values onto the stack is not considered allocating). Because the pointer to the heap is a known, fixed size, you can store the pointer on the stack, but when you want the actual data, you must follow the pointer. Think of being seated at a restaurant. When you enter, you state the number of people in your group, and the host finds an empty table that fits everyone and leads you there. If someone in your group comes late, they can ask where you’ve been seated to find you.

Pushing to the stack is faster than allocating on the heap because the allocator never has to search for a place to store new data; that location is always at the top of the stack. Comparatively, allocating space on the heap requires more work because the allocator must first find a big enough space to hold the data and then perform bookkeeping to prepare for the next allocation.

Accessing data in the heap is slower than accessing data on the stack because you have to follow a pointer to get there. Contemporary processors are faster if they jump around less in memory. Continuing the analogy, consider a server at a restaurant taking orders from many tables. It’s most efficient to get all the orders at one table before moving on to the next table. Taking an order from table A, then an order from table B, then one from A again, and then one from B again would be a much slower process. By the same token, a processor can do its job better if it works on data that’s close to other data (as it is on the stack) rather than farther away (as it can be on the heap).

When your code calls a function, the values passed into the function (including, potentially, pointers to data on the heap) and the function’s local variables get pushed onto the stack. When the function is over, those values get popped off the stack.

Keeping track of what parts of code are using what data on the heap, minimizing the amount of duplicate data on the heap, and cleaning up unused data on the heap so you don’t run out of space are all problems that ownership addresses. Once you understand ownership, you won’t need to think about the stack and the heap very often, but knowing that the main purpose of ownership is to manage heap data can help explain why it works the way it does.

Ownership Explained

Ownership is a set of rules that govern how a Rust program manages memory. All programs have to manage the way they use a computer’s memory while running. Some languages have garbage collection that regularly looks for no-longer-used memory as the program runs; in other languages, the programmer must explicitly allocate and free the memory. Rust uses a third approach: memory is managed through a system of ownership with a set of rules that the compiler checks. If any of the rules are violated, the program won’t compile. None of the features of ownership will slow down your program while it’s running.

Ownership Rules

  • Each value in Rust has an owner.
  • There can only be one owner at a time.
  • When the owner goes out of scope, the value will be dropped.
#![allow(unused)]
fn main() {
let s = "hello";
}

Variable Scope

  • When s comes into scope, it is valid.
  • It remains valid until it goes out of scope.

The String Type

The types covered previously are of a known size, can be stored on the stack and popped off the stack when their scope is over, and can be quickly and trivially copied to make a new, independent instance if another part of code needs to use the same value in a different scope. But we want to look at data that is stored on the heap and explore how Rust knows when to clean up that data, and the String type is a great example.

We’ve already seen string literals (like the variable s above), where a string value is hardcoded into our program. String literals are convenient, but they aren’t suitable for every situation in which we may want to use text. One reason is that they’re immutable.

Rust has a second string type, String. This type manages data allocated on the heap and as such is able to store an amount of text that is unknown to us at compile time. You can create a String from a string literal using the from function:

#![allow(unused)]
fn main() {
let s = String::from("hello");
}

The double colon :: operator allows us to namespace this particular from function under the String type rather than using some sort of name like string_from.

#![allow(unused)]
fn main() {
let mut s = String::from("hello");

s.push_str(", world"); // push_str() appends a lieteral to a string

println!("{s}");
}

Memory and Allocation

In the case of a string literal, we know the contents at compile time, so the text is hardcoded directly into the final executable. This is why string literals are fast and efficient. But these properties only come from the string literal’s immutability.

With the String type, in order to support a mutable, growable piece of text, we need to allocate an amount of memory on the heap, unknown at compile time, to hold the contents. This means:

  • The memory must be requested from the memory allocator at runtime.
  • We need a way of returning this memory to the allocator when we’re done with our String.

Rust takes a different path: the memory is automatically returned once the variable that owns it goes out of scope.

#![allow(unused)]
fn main() {
{
    let s = String::from("hello");
    // s is valid from this point forward

    // do whatever with s
} // this scope is now over and s is no longer valid
}

There is a natural point at which we can return the memory our String needs to the allocator: when s goes out of scope. When a variable goes out of scope, Rust calls a special function for us. This function is called drop, and it’s where the author of String can put the code to return the memory. Rust calls drop automatically at the closing curly bracket.

Variables and Data Interacting with Move

A String is made up of three parts, shown on the left: a pointer to the memory that holds the contents of the string, a length, and a capacity. This group of data is stored on the stack. On the right is the memory on the heap that holds the contents.

#![allow(unused)]
fn main() {
let s1 = String::from("hello");
let s2 = s1;
}

When we assign s1 to s2, the String data is copied, meaning we copy the pointer, the length, and the capacity that are on the stack. We do not copy the data on the heap that the pointer refers to.

The representation does not look like the picture below, which is what memory would look like if Rust instead copied the heap data as well. If Rust did this, the operation s2 = s1 could be very expensive in terms of runtime performance if the data on the heap were large.

Earlier, we said that when a variable goes out of scope, Rust automatically calls the drop function and cleans up the heap memory for that variable. But two images above shows both data pointers pointing to the same location. This is a problem: when s2 and s1 go out of scope, they will both try to free the same memory. This is known as a double free error and is one of the memory safety bugs we mentioned previously. Freeing memory twice can lead to memory corruption, which can potentially lead to security vulnerabilities. To ensure memory safety, after the line let s2 = s1;, Rust considers s1 as no longer valid.

You should receive an error on the following code

#![allow(unused)]
fn main() {
let s1 = String::from("Hello");
let s2 = s1;

println!("{s1}, world");
}

If you’ve heard the terms shallow copy and deep copy while working with other languages, the concept of copying the pointer, length, and capacity without copying the data probably sounds like making a shallow copy. But because Rust also invalidates the first variable, instead of being called a shallow copy, it’s known as a move. In this example, we would say that s1 was moved into s2.

Rust will never automatically create “deep” copies of your data. Therefore, any automatic copying can be assumed to be inexpensive in terms of runtime performance.

Variables and Data Interacting with Clone

If we do want to deeply copy the heap data of the String, not just the stack data, we can use a common method called clone.

#![allow(unused)]
fn main() {
let s1 = String::from("Hello");
let s2 = s1.clone();

println!("s1 = {s1}, s2 = {s2}");
}

When you see a call to `clone``, you know that some arbitrary code is being executed and that code may be expensive. It’s a visual indicator that something different is going on.

Stack Only Data: Copy

#![allow(unused)]
fn main() {
let x = 5;
let y = x;

println!("x = {x}, y = {y}");
}

But this code seems to contradict what we just learned: we don’t have a call to clone, but x is still valid and wasn’t moved into y. The reason is that types such as integers that have a known size at compile time are stored entirely on the stack, so copies of the actual values are quick to make. That means there’s no reason we would want to prevent x from being valid after we create the variable y. In other words, there’s no difference between deep and shallow copying here, so calling clone wouldn’t do anything different from the usual shallow copying, and we can leave it out.

So, what types implement the Copy trait? You can check the documentation for the given type to be sure, but as a general rule, any group of simple scalar values can implement Copy, and nothing that requires allocation or is some form of resource can implement Copy. Here are some of the types that implement Copy:

  • All the integer types, such as u32.
  • The Boolean type, bool, with values true and false.
  • All the floating-point types, such as f64.
  • The character type, char.
  • Tuples, if they only contain types that also implement Copy. For example, (i32, i32) implements Copy, but (i32, String) does not.

Ownership and Functions

fn main() {
    let s = String::from("hello");  // s comes into scope

    takes_ownership(s);             // s's value moves into the function...
                                    // ... and so is no longer valid here

    let x = 5;                      // x comes into scope

    makes_copy(x);                  // x would move into the function,
                                    // but i32 is Copy, so it's okay to still
                                    // use x afterward

} // Here, x goes out of scope, then s. However, because s's value was moved,
  // nothing special happens.

fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{some_string}");
} // Here, some_string goes out of scope and `drop` is called. The backing
  // memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{some_integer}");
} // Here, some_integer goes out of scope. Nothing special happens.

If we tried to use s after the call to takes_ownership, Rust would throw a compile-time error.

Return Values and Scope

Returning values can also transfer ownership.

fn main() {
    let s1 = gives_ownership();         // gives_ownership moves its return
                                        // value into s1

    let s2 = String::from("hello");     // s2 comes into scope

    let s3 = takes_and_gives_back(s2);  // s2 is moved into
                                        // takes_and_gives_back, which also
                                        // moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
  // happens. s1 goes out of scope and is dropped.

fn gives_ownership() -> String {        // gives_ownership will move its
                                        // return value into the function
                                        // that calls it

    let some_string = String::from("yours"); // some_string comes into scope

    some_string                              // some_string is returned and
                                             // moves out to the calling
                                             // function
}

// This function takes a String and returns a String.
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
                                                      // scope

    a_string  // a_string is returned and moves out to the calling function
}

While this works, taking ownership and then returning ownership with every function is a bit tedious.

fn main() {
    let s1 = String::from("hello");

    let (s2, len) = calculate_length(s1);

    println!("The length of '{s2}' is {len}.");
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len(); // len() returns the length of a String

    (s, length)
}

But this is too much ceremony and a lot of work for a concept that should be common. Luckily for us, Rust has a feature for using a value without transferring ownership, called references.

References and Borrowing

Exercises

The Slice Type

Structuring Data

Structs

Enums

Pattern Matching

Error Handling

Generics, Traits, and Lifetimes

Iterators and Closures

Closures

Iterators

Pointers

Using Box

Dereferencing

Drop Trait

Rc

RefCell

Reference Cycles

Concurrency

Using Threads

Message Passing

Shared State Concurrency

Send and Sync

Object Oriented Features

Characteristics of Object Oriented Programming

Using Trait Objects

Implementing OO Design Pattern

Patterns and Matching

Where Patterns can be used

Refutability

Pattern Syntax

Advanced Features

Unsafe Rust

Advanced Traits

Advanced Types

Advanced Functions and Closures

Macros

Reference

Appendix A: Keywords

The following list contains keywords that are reserved for current or future use by the Rust language. As such, they cannot be used as identifiers (except as raw identifiers as we’ll discuss in the “Raw Identifiers” section). Identifiers are names of functions, variables, parameters, struct fields, modules, crates, constants, macros, static values, attributes, types, traits, or lifetimes.

Keywords Currently in Use

The following is a list of keywords currently in use, with their functionality described.

  • as - perform primitive casting, disambiguate the specific trait containing an item, or rename items in use statements
  • async - return a Future instead of blocking the current thread
  • await - suspend execution until the result of a Future is ready
  • break - exit a loop immediately
  • const - define constant items or constant raw pointers
  • continue - continue to the next loop iteration
  • crate - in a module path, refers to the crate root
  • dyn - dynamic dispatch to a trait object
  • else - fallback for if and if let control flow constructs
  • enum - define an enumeration
  • extern - link an external function or variable
  • false - Boolean false literal
  • fn - define a function or the function pointer type
  • for - loop over items from an iterator, implement a trait, or specify a higher-ranked lifetime
  • if - branch based on the result of a conditional expression
  • impl - implement inherent or trait functionality
  • in - part of for loop syntax
  • let - bind a variable
  • loop - loop unconditionally
  • match - match a value to patterns
  • mod - define a module
  • move - make a closure take ownership of all its captures
  • mut - denote mutability in references, raw pointers, or pattern bindings
  • pub - denote public visibility in struct fields, impl blocks, or modules
  • ref - bind by reference
  • return - return from function
  • Self - a type alias for the type we are defining or implementing
  • self - method subject or current module
  • static - global variable or lifetime lasting the entire program execution
  • struct - define a structure
  • super - parent module of the current module
  • trait - define a trait
  • true - Boolean true literal
  • type - define a type alias or associated type
  • union - define a union; is only a keyword when used in a union declaration
  • unsafe - denote unsafe code, functions, traits, or implementations
  • use - bring symbols into scope
  • where - denote clauses that constrain a type
  • while - loop conditionally based on the result of an expression

Keywords Reserved for Future Use

The following keywords do not yet have any functionality but are reserved by Rust for potential future use.

  • abstract
  • become
  • box
  • do
  • final
  • macro
  • override
  • priv
  • try
  • typeof
  • unsized
  • virtual
  • yield

Raw Identifiers

Raw identifiers are the syntax that lets you use keywords where they wouldn’t normally be allowed. You use a raw identifier by prefixing a keyword with r#.

For example, match is a keyword. If you try to compile the following function that uses match as its name:

Filename: src/main.rs

fn match(needle: &str, haystack: &str) -> bool {
    haystack.contains(needle)
}

you’ll get this error:

error: expected identifier, found keyword `match`
 --> src/main.rs:4:4
  |
4 | fn match(needle: &str, haystack: &str) -> bool {
  |    ^^^^^ expected identifier, found keyword

The error shows that you can’t use the keyword match as the function identifier. To use match as a function name, you need to use the raw identifier syntax, like this:

Filename: src/main.rs

fn r#match(needle: &str, haystack: &str) -> bool {
    haystack.contains(needle)
}

fn main() {
    assert!(r#match("foo", "foobar"));
}

This code will compile without any errors. Note the r# prefix on the function name in its definition as well as where the function is called in main.

Raw identifiers allow you to use any word you choose as an identifier, even if that word happens to be a reserved keyword. This gives us more freedom to choose identifier names, as well as lets us integrate with programs written in a language where these words aren’t keywords. In addition, raw identifiers allow you to use libraries written in a different Rust edition than your crate uses. For example, try isn’t a keyword in the 2015 edition but is in the 2018 edition. If you depend on a library that’s written using the 2015 edition and has a try function, you’ll need to use the raw identifier syntax, r#try in this case, to call that function from your 2018 edition code. See Appendix E for more information on editions.

Appendix B: Operators and Symbols

This appendix contains a glossary of Rust’s syntax, including operators and other symbols that appear by themselves or in the context of paths, generics, trait bounds, macros, attributes, comments, tuples, and brackets.

Operators

Table B-1 contains the operators in Rust, an example of how the operator would appear in context, a short explanation, and whether that operator is overloadable. If an operator is overloadable, the relevant trait to use to overload that operator is listed.

Table B-1: Operators

OperatorExampleExplanationOverloadable?
!`ident!(...)`, `ident!{...}`, `ident![...]`Macro expansion
!!exprBitwise or logical complement`Not`
!=`expr != expr`Nonequality comparison`PartialEq`
%`expr % expr`Arithmetic remainder`Rem`
%=`var %= expr`Arithmetic remainder and assignment`RemAssign`
&&expr, &mut exprBorrow
&&type, &mut type, &'a type, &'a mut typeBorrowed pointer type
&`expr & expr`Bitwise AND`BitAnd`
&=`var &= expr`Bitwise AND and assignment`BitAndAssign`
&&`expr && expr`Short-circuiting logical AND
*`expr * expr`Arithmetic multiplication`Mul`
*=`var *= expr`Arithmetic multiplication and assignment`MulAssign`
**exprDereference`Deref`
*\*const type, \*mut typeRaw pointer
+`trait + trait`, 'a + traitCompound type constraint
+`expr + expr`Arithmetic addition`Add`
+=`var += expr`Arithmetic addition and assignment`AddAssign`
,`expr, expr`Argument and element separator
\-\- exprArithmetic negation`Neg`
-`expr - expr`Arithmetic subtraction`Sub`
-=`var -= expr`Arithmetic subtraction and assignment`SubAssign`
->`fn(...) -> type`, <code>|...| -> type</code>Function and closure return type
.`expr.ident`Member access
...., `expr..`, ..expr, `expr..expr`Right-exclusive range literal`PartialOrd`
..=..=expr, `expr..=expr`Right-inclusive range literal`PartialOrd`
....exprStruct literal update syntax
..`variant(x, ..)`, `struct_type { x, .. }`“And the rest” pattern binding
...`expr...expr`(Deprecated, use ..= instead) In a pattern: inclusive range pattern
/`expr / expr`Arithmetic division`Div`
/=`var /= expr`Arithmetic division and assignment`DivAssign`
:`pat: type`, `ident: type`Constraints
:`ident: expr`Struct field initializer
:'a: loop {...}Loop label
;`expr;`Statement and item terminator
;\[...; len\]Part of fixed-size array syntax
<<`expr << expr`Left-shift`Shl`
<<=`var <<= expr`Left-shift and assignment`ShlAssign`
<`expr < expr`Less than comparison`PartialOrd`
<=`expr <= expr`Less than or equal to comparison`PartialOrd`
=`var = expr`, `ident = type`Assignment/equivalence
==`expr == expr`Equality comparison`PartialEq`
=>`pat => expr`Part of match arm syntax
>`expr > expr`Greater than comparison`PartialOrd`
>=`expr >= expr`Greater than or equal to comparison`PartialOrd`
>>`expr >> expr`Right-shift`Shr`
>>=`var >>= expr`Right-shift and assignment`ShrAssign`
@`ident @ pat`Pattern binding
^`expr ^ expr`Bitwise exclusive OR`BitXor`
^=`var ^= expr`Bitwise exclusive OR and assignment`BitXorAssign`
<code>|</code><code>pat | pat</code>Pattern alternatives
<code>|</code><code>expr | expr</code>Bitwise OR`BitOr`
<code>|=</code><code>var |= expr</code>Bitwise OR and assignment`BitOrAssign`
<code>||</code><code>expr || expr</code>Short-circuiting logical OR
?`expr?`Error propagation

Non-operator Symbols

The following list contains all symbols that don’t function as operators; that is, they don’t behave like a function or method call.

Table B-2 shows symbols that appear on their own and are valid in a variety of locations.

Table B-2: Stand-Alone Syntax

SymbolExplanation
'identNamed lifetime or loop label
...u8, ...i32, ...f64, ...usize, etc.Numeric literal of specific type
"..."String literal
`r"..."`, `r#"..."#`, `r##"..."##`, etc.Raw string literal, escape characters not processed
`b"..."`Byte string literal; constructs an array of bytes instead of a string
`br"..."`, `br#"..."#`, `br##"..."##`, etc.Raw byte string literal, combination of raw and byte string literal
'...'Character literal
`b'...'`ASCII byte literal
<code>|...| expr</code>Closure
!Always empty bottom type for diverging functions
`_`“Ignored” pattern binding; also used to make integer literals readable

Table B-3 shows symbols that appear in the context of a path through the module hierarchy to an item.

Table B-3: Path-Related Syntax

SymbolExplanation
`ident::ident`Namespace path
::pathPath relative to the crate root (i.e., an explicitly absolute path)
`self::path`Path relative to the current module (i.e., an explicitly relative path).
`super::path`Path relative to the parent of the current module
`type::ident`, &lt;type as trait&gt;::identAssociated constants, functions, and types
&lt;type&gt;::...Associated item for a type that cannot be directly named (e.g., &lt;&T&gt;::..., &lt;\[T\]&gt;::..., etc.)
`trait::method(...)`Disambiguating a method call by naming the trait that defines it
`type::method(...)`Disambiguating a method call by naming the type for which it’s defined
&lt;type as trait&gt;::method(...)Disambiguating a method call by naming the trait and type

Table B-4 shows symbols that appear in the context of using generic type parameters.

Table B-4: Generics

SymbolExplanation
`path<...>`Specifies parameters to generic type in a type (e.g., `Vec<u8>`)
`path::<...>`, `method::<...>`Specifies parameters to generic type, function, or method in an expression; often referred to as turbofish (e.g., "42".parse::&lt;i32&gt;())
`fn ident<...> ...`Define generic function
`struct ident<...> ...`Define generic structure
`enum ident<...> ...`Define generic enumeration
`impl<...> ...`Define generic implementation
`for<...> type`Higher-ranked lifetime bounds
`type<ident=type>`A generic type where one or more associated types have specific assignments (e.g., `Iterator<Item=T>`)

Table B-5 shows symbols that appear in the context of constraining generic type parameters with trait bounds.

Table B-5: Trait Bound Constraints

SymbolExplanation
`T: U`Generic parameter `T` constrained to types that implement `U`
`T: 'a`Generic type `T` must outlive lifetime 'a (meaning the type cannot transitively contain any references with lifetimes shorter than 'a)
`T: 'static`Generic type `T` contains no borrowed references other than 'static ones
'b: 'aGeneric lifetime 'b must outlive lifetime 'a
`T: ?Sized`Allow generic type parameter to be a dynamically sized type
'a + trait, `trait + trait`Compound type constraint

Table B-6 shows symbols that appear in the context of calling or defining macros and specifying attributes on an item.

Table B-6: Macros and Attributes

SymbolExplanation
#\[meta\]Outer attribute
#!\[meta\]Inner attribute
$identMacro substitution
$ident:kindMacro capture
$(…)…Macro repetition
`ident!(...)`, `ident!{...}`, `ident![...]`Macro invocation

Table B-7 shows symbols that create comments.

Table B-7: Comments

SymbolExplanation
//Line comment
//!Inner line doc comment
///Outer line doc comment
/*...*/Block comment
/*!...*/Inner block doc comment
/**...*/Outer block doc comment

Table B-8 shows symbols that appear in the context of using tuples.

Table B-8: Tuples

SymbolExplanation
()Empty tuple (aka unit), both literal and type
(expr)Parenthesized expression
(expr,)Single-element tuple expression
(type,)Single-element tuple type
(expr, ...)Tuple expression
(type, ...)Tuple type
`expr(expr, ...)`Function call expression; also used to initialize tuple `struct`s and tuple `enum` variants
`expr.0`, `expr.1`, etc.Tuple indexing

Table B-9 shows the contexts in which curly braces are used.

Table B-9: Curly Brackets

ContextExplanation
{...}Block expression
`Type {...}``struct` literal

Table B-10 shows the contexts in which square brackets are used.

Table B-10: Square Brackets

ContextExplanation
\[...\]Array literal
\[expr; len\]Array literal containing `len` copies of `expr`
\[type; len\]Array type containing `len` instances of `type`
`expr[expr]`Collection indexing. Overloadable (`Index`, `IndexMut`)
`expr[..]`, `expr[a..]`, `expr[..b]`, `expr[a..b]`Collection indexing pretending to be collection slicing, using `Range`, `RangeFrom`, `RangeTo`, or `RangeFull` as the “index”

Appendix C: Derivable Traits

In various places in the book, we’ve discussed the derive attribute, which you can apply to a struct or enum definition. The derive attribute generates code that will implement a trait with its own default implementation on the type you’ve annotated with the derive syntax.

In this appendix, we provide a reference of all the traits in the standard library that you can use with derive. Each section covers:

  • What operators and methods deriving this trait will enable
  • What the implementation of the trait provided by derive does
  • What implementing the trait signifies about the type
  • The conditions in which you’re allowed or not allowed to implement the trait
  • Examples of operations that require the trait

If you want different behavior from that provided by the derive attribute, consult the standard library documentation for each trait for details of how to manually implement them.

These traits listed here are the only ones defined by the standard library that can be implemented on your types using derive. Other traits defined in the standard library don’t have sensible default behavior, so it’s up to you to implement them in the way that makes sense for what you’re trying to accomplish.

An example of a trait that can’t be derived is Display, which handles formatting for end users. You should always consider the appropriate way to display a type to an end user. What parts of the type should an end user be allowed to see? What parts would they find relevant? What format of the data would be most relevant to them? The Rust compiler doesn’t have this insight, so it can’t provide appropriate default behavior for you.

The list of derivable traits provided in this appendix is not comprehensive: libraries can implement derive for their own traits, making the list of traits you can use derive with truly open-ended. Implementing derive involves using a procedural macro, which is covered in the “Macros” section of Chapter 19.

Debug for Programmer Output

The Debug trait enables debug formatting in format strings, which you indicate by adding :? within {} placeholders.

The Debug trait allows you to print instances of a type for debugging purposes, so you and other programmers using your type can inspect an instance at a particular point in a program’s execution.

The Debug trait is required, for example, in use of the assert_eq! macro. This macro prints the values of instances given as arguments if the equality assertion fails so programmers can see why the two instances weren’t equal.

PartialEq and Eq for Equality Comparisons

The PartialEq trait allows you to compare instances of a type to check for equality and enables use of the == and != operators.

Deriving PartialEq implements the eq method. When PartialEq is derived on structs, two instances are equal only if all fields are equal, and the instances are not equal if any fields are not equal. When derived on enums, each variant is equal to itself and not equal to the other variants.

The PartialEq trait is required, for example, with the use of the assert_eq! macro, which needs to be able to compare two instances of a type for equality.

The Eq trait has no methods. Its purpose is to signal that for every value of the annotated type, the value is equal to itself. The Eq trait can only be applied to types that also implement PartialEq, although not all types that implement PartialEq can implement Eq. One example of this is floating point number types: the implementation of floating point numbers states that two instances of the not-a-number (NaN) value are not equal to each other.

An example of when Eq is required is for keys in a HashMap<K, V> so the HashMap<K, V> can tell whether two keys are the same.

PartialOrd and Ord for Ordering Comparisons

The PartialOrd trait allows you to compare instances of a type for sorting purposes. A type that implements PartialOrd can be used with the <, >, <=, and >= operators. You can only apply the PartialOrd trait to types that also implement PartialEq.

Deriving PartialOrd implements the partial_cmp method, which returns an Option<Ordering> that will be None when the values given don’t produce an ordering. An example of a value that doesn’t produce an ordering, even though most values of that type can be compared, is the not-a-number (NaN) floating point value. Calling partial_cmp with any floating point number and the NaN floating point value will return None.

When derived on structs, PartialOrd compares two instances by comparing the value in each field in the order in which the fields appear in the struct definition. When derived on enums, variants of the enum declared earlier in the enum definition are considered less than the variants listed later.

The PartialOrd trait is required, for example, for the gen_range method from the rand crate that generates a random value in the range specified by a range expression.

The Ord trait allows you to know that for any two values of the annotated type, a valid ordering will exist. The Ord trait implements the cmp method, which returns an Ordering rather than an Option<Ordering> because a valid ordering will always be possible. You can only apply the Ord trait to types that also implement PartialOrd and Eq (and Eq requires PartialEq). When derived on structs and enums, cmp behaves the same way as the derived implementation for partial_cmp does with PartialOrd.

An example of when Ord is required is when storing values in a BTreeSet<T>, a data structure that stores data based on the sort order of the values.

Clone and Copy for Duplicating Values

The Clone trait allows you to explicitly create a deep copy of a value, and the duplication process might involve running arbitrary code and copying heap data. See the “Ways Variables and Data Interact: Clone” section in Chapter 4 for more information on Clone.

Deriving Clone implements the clone method, which when implemented for the whole type, calls clone on each of the parts of the type. This means all the fields or values in the type must also implement Clone to derive Clone.

An example of when Clone is required is when calling the to_vec method on a slice. The slice doesn’t own the type instances it contains, but the vector returned from to_vec will need to own its instances, so to_vec calls clone on each item. Thus, the type stored in the slice must implement Clone.

The Copy trait allows you to duplicate a value by only copying bits stored on the stack; no arbitrary code is necessary. See the “Stack-Only Data: Copy” section in Chapter 4 for more information on Copy.

The Copy trait doesn’t define any methods to prevent programmers from overloading those methods and violating the assumption that no arbitrary code is being run. That way, all programmers can assume that copying a value will be very fast.

You can derive Copy on any type whose parts all implement Copy. A type that implements Copy must also implement Clone, because a type that implements Copy has a trivial implementation of Clone that performs the same task as Copy.

The Copy trait is rarely required; types that implement Copy have optimizations available, meaning you don’t have to call clone, which makes the code more concise.

Everything possible with Copy you can also accomplish with Clone, but the code might be slower or have to use clone in places.

Hash for Mapping a Value to a Value of Fixed Size

The Hash trait allows you to take an instance of a type of arbitrary size and map that instance to a value of fixed size using a hash function. Deriving Hash implements the hash method. The derived implementation of the hash method combines the result of calling hash on each of the parts of the type, meaning all fields or values must also implement Hash to derive Hash.

An example of when Hash is required is in storing keys in a HashMap<K, V> to store data efficiently.

Default for Default Values

The Default trait allows you to create a default value for a type. Deriving Default implements the default function. The derived implementation of the default function calls the default function on each part of the type, meaning all fields or values in the type must also implement Default to derive Default.

The Default::default function is commonly used in combination with the struct update syntax discussed in the “Creating Instances From Other Instances With Struct Update Syntax” section in Chapter 5. You can customize a few fields of a struct and then set and use a default value for the rest of the fields by using ..Default::default().

The Default trait is required when you use the method unwrap_or_default on Option<T> instances, for example. If the Option<T> is None, the method unwrap_or_default will return the result of Default::default for the type T stored in the Option<T>.

Useful Development Tools

In this appendix, we talk about some useful development tools that the Rust project provides. We’ll look at automatic formatting, quick ways to apply warning fixes, a linter, and integrating with IDEs.

Automatic Formatting with rustfmt

The rustfmt tool reformats your code according to the community code style. Many collaborative projects use rustfmt to prevent arguments about which style to use when writing Rust: everyone formats their code using the tool.

To install rustfmt, enter the following:

$ rustup component add rustfmt

This command gives you rustfmt and cargo-fmt, similar to how Rust gives you both rustc and cargo. To format any Cargo project, enter the following:

$ cargo fmt

Running this command reformats all the Rust code in the current crate. This should only change the code style, not the code semantics. For more information on rustfmt, see its documentation.

Fix Your Code with rustfix

The rustfix tool is included with Rust installations and can automatically fix compiler warnings that have a clear way to correct the problem that’s likely what you want. It’s likely you’ve seen compiler warnings before. For example, consider this code:

Filename: src/main.rs

fn do_something() {}

fn main() {
    for i in 0..100 {
        do_something();
    }
}

Here, we’re calling the do_something function 100 times, but we never use the variable i in the body of the for loop. Rust warns us about that:

$ cargo build
   Compiling myprogram v0.1.0 (file:///projects/myprogram)
warning: unused variable: `i`
 --> src/main.rs:4:9
  |
4 |     for i in 0..100 {
  |         ^ help: consider using `_i` instead
  |
  = note: #[warn(unused_variables)] on by default

    Finished dev [unoptimized + debuginfo] target(s) in 0.50s

The warning suggests that we use _i as a name instead: the underscore indicates that we intend for this variable to be unused. We can automatically apply that suggestion using the rustfix tool by running the command cargo fix:

$ cargo fix
    Checking myprogram v0.1.0 (file:///projects/myprogram)
      Fixing src/main.rs (1 fix)
    Finished dev [unoptimized + debuginfo] target(s) in 0.59s

When we look at src/main.rs again, we’ll see that cargo fix has changed the code:

Filename: src/main.rs

fn do_something() {}

fn main() {
    for _i in 0..100 {
        do_something();
    }
}

The for loop variable is now named _i, and the warning no longer appears.

You can also use the cargo fix command to transition your code between different Rust editions. Editions are covered in Appendix E.

More Lints with Clippy

The Clippy tool is a collection of lints to analyze your code so you can catch common mistakes and improve your Rust code.

To install Clippy, enter the following:

$ rustup component add clippy

To run Clippy’s lints on any Cargo project, enter the following:

$ cargo clippy

For example, say you write a program that uses an approximation of a mathematical constant, such as pi, as this program does:

Filename: src/main.rs

fn main() {
    let x = 3.1415;
    let r = 8.0;
    println!("the area of the circle is {}", x * r * r);
}

Running cargo clippy on this project results in this error:

error: approximate value of `f{32, 64}::consts::PI` found
 --> src/main.rs:2:13
  |
2 |     let x = 3.1415;
  |             ^^^^^^
  |
  = note: `#[deny(clippy::approx_constant)]` on by default
  = help: consider using the constant directly
  = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant

This error lets you know that Rust already has a more precise PI constant defined, and that your program would be more correct if you used the constant instead. You would then change your code to use the PI constant. The following code doesn’t result in any errors or warnings from Clippy:

Filename: src/main.rs

fn main() {
    let x = std::f64::consts::PI;
    let r = 8.0;
    println!("the area of the circle is {}", x * r * r);
}

For more information on Clippy, see its documentation.

IDE Integration Using rust-analyzer

To help IDE integration, the Rust community recommends using rust-analyzer. This tool is a set of compiler-centric utilities that speaks the Language Server Protocol, which is a specification for IDEs and programming languages to communicate with each other. Different clients can use rust-analyzer, such as the Rust analyzer plug-in for Visual Studio Code.

Visit the rust-analyzer project’s home page for installation instructions, then install the language server support in your particular IDE. Your IDE will gain abilities such as autocompletion, jump to definition, and inline errors.

Random Code Samples

Greatest Common Denominator

Uses the Euclidean Algorithm to get Greatest Common Denominator

use std::str::FromStr;
use std::env;

fn gcd(mut n: u64, mut m: u64) -> u64 {
    // Make sure that either number isn't 0
    assert!(n != 0 && m != 0);

    // Run a loop while the remainder `m` is not 0
    while m != 0 {
        // Make sure we are always trying to get the divsor of
        // the smaller number
        if m < n {
            // initialize a temp variable
            let temp = m;
            m = n;
            n = temp;
        }
        // Get the divisor
        m = m % n;
    }
    // return n aka the greatest common denominator
    n
}

#[test]
fn test_gcd() {
    assert_eq!(gcd(14, 15), 1);

    assert_eq!(gcd(8, 4), 4);
}

fn main() {
    // Initiate the vector to put in you CLI args
    let mut numbers = vec![];

    // Parse the CLI args
    // The first arg is aleays the name of the program so skip it
    for arg in env::args().skip(1) {
        numbers.push(u64::from_str(&arg)
                    // Parse the arg and throw an error iff there is an issue
                    .expect("error parsing argument"));
    }

    // Throw error and exit if there are no args
    if numbers.len() == 0 {
        eprintln!("Usage: gcd NUMBER ...");
        std::process::exit(1);
    }

    // Iterate through the args
    let mut d = numbers[0];
    for m in &numbers[1..] {
        d = gcd(d, *m);
    }

    // Print the gcd
    println!("The greatest common divisor of {:?} is {}",
            numbers, d);
}

Serving a Web Page

Serves the gcd() function from GCD.

use actix_web::{web, App, HttpResponse, HttpServer};
use serde::Deserialize;

#[derive(Deserialize)]
struct GcdParameters {
    n: u64,
    m: u64,
}

fn main() {
    let server = HttpServer::new(|| {
        App::new()
            .route("/", web::get().to(get_index))
    });

    println!("Serving on http://localhost:3000...")
    server
        .bind("0.0.0.0:3000").expect("error binding to the server address")
        .run().expect("error running server");
}

fn get_index() -> HttpResponse {
    HttpResponse::Ok()
        .content_type("text/html")
        .body(
            r#"
                <title>GCD Calculator</title>
                <form action="/gcd" method="post">
                <input type="text" name="n"/>
                <input type="text" name="m"/>
                <button type="submit">Compute GCD</button>
                </form>
            "#,
        )
}

fn gcd(mut n: u64, mut m: u64) -> u64 {
    // Make sure that either number isn't 0
    assert!(n != 0 && m != 0);

    // Run a loop while the remainder `m` is not 0
    while m != 0 {
        // Make sure we are always trying to get the divsor of
        // the smaller number
        if m < n {
            // initialize a temp variable
            let temp = m;
            m = n;
            n = temp;
        }
        // Get the divisor
        m = m % n;
    }
    // return n aka the greatest common denominator
    n
}

fn post_gcd(form: web::Form<GcdParameters>) -> HttpResponse {
    if form.n ==0 || form.m == 0 {
        return HttpResponse::BadRequest()
            .content_type("text/html")
            .body("Computing the GCD with 0 is boring.");
    }

    let response = 
        format!("The greatest common divisor of the numbers {} and )
}

Mandelbrot

Mandelbrot code sample

// Use the num crate to bring in complex numbers
use num::Complex;
use std::str::FromStr;
use image::ColorType;
use image::png::PNGEncoder;
use std::fs::File;
use std::env;

fn complex_square_add_loop(c: Complex<f64>) {
    let mut z = Complex { re: 0.0, im: 0.0 };
    loop {
        z = z * z + c;
    }
}

fn escape_time(c: Complex<f64>, limit: usize) -> Option<usize> {
    let mut z = Complex { re: 0.0, im: 0.0};
    for i in 0..limit {
        if z.norm_sqr() > 4.0 {
            return Some(i);
        }
        z = z * z + c;
    }

    None
}

fn parse_pair<T: FromStr>(s: &str, separator: char) -> Option<(T, T)> {
    match s.find(separator) {
        None => None,
        Some(Index) => {
            match (T::from_str(&s[..index]), T::from_str(&s[index + 1..])) {
                (Ok(l), Ok(r)) => Some((l, r)),
                _ => None
            }
        }
    }
}

#[test]
fn test_parse_pair() {
    assert_eq!(parse_pair::<i32>("",        ','), None);
}

fn pixel_to_point(bounds: (usize, usize),
                  pixel: (usize, usize),
                  upper_left: Complex<f64>,
                  lower_right: Complex<f64>)
            -> Complex<f64>
{
    let (width, height) = (lower_right.re - upper_left.re,
                           upper_left.im - lower_right.im);

    Complex {
        re: upper_left.re + pixel.0 as f64 * width / bounds.0 as f64,
        im: upper_left.im - pixel.1 as f64 * height / bounds.1 as f64
    }
}

#[test]
fn test_pixel_to_point() {
    assert_eq!(pixel_to_point((100, 200), (25, 175),
                              Complex { re: -1.0, im:  1.0 },
                              Complex { re:  1.0, im: -1.0 }),
               Complex { re: -0.5, im: -0.75 });
}

fn render(pixels: &mut[u8],
          bounds: (usize, usize),
          upper_left: Complex<f64>,
          lower_right: Complex<f64>)
{
    assert!(pizels.len() == bounds.0 * bounds.1);

    for row in 0..bounds.1 {
        for column in 0..bounds.0 {
            let point = pixel_to_point(bounds, (column, row),
                                       upper_left, lower_right);
            pixels[row * bounds.0 + column] = 
                match escape_time(point, 255) {
                    None => 0,
                    Some(count) => 255 - count as u8
                };
        }
    }
}

fn write_image(filename: &str, pixels: &[u8], bounds: (usize, usize))
    -> Result<(), std::io::Error>
{
    let output = File::create(filename)?;

    let encoder = PNGEncoder::new(output);
    encoder.encode(pixels,
                   bounds.0 as u32, bounds.1 as u32,
                   ColorType::Gray(8))?;

    Ok(())
}

fn main() {
    let args: Vec<String> = env::args().collect();

    if args.len() != 5 {
        eprintln!("Usage: {} FILE PIXELS UPPERLEFT LOWERRIGHT", args[0]);
        eprintln!("Example: {} mandel.png 1000X750 -1.20,0.35 -1,0.20", args[0]);
        std::process::exit(1);
    }

    let bounds = parse_pair(&args[2], 'x')
        .expect("error parsing image dimensions");
    let upper_left = parse_complex(&args[3])
        .expect("error parsing upper left corner point");
    let lower_right = parse_complex(&args[4])
        .expect("error parsing lower right corner");

    let mut pixels = vec![0; bounds.0 * bounds.1];

    render(&mut pixels, bounds, upper_left, lower_right);

    write_image(&args[1], &pixels, bounds)
        .expect("error writing PNG file");
}