Learning Rust the Hands-On Way

Performant Software Systems with Rust — Lecture 2

Baochun Li, Professor
Department of Electrical and Computer Engineering
University of Toronto
  • Rust is one of the most exciting languages in the recent years

  • Yet, it is known to be a complex language with a steep learning curve

But I’ve got some good news for you.

It can be simple if you learn it the hands-on way.

Advanced, yet friendly, compiler

Unhelpful, cryptic errors abound

JSONDecodingError On Line 1

NullPointerException

IndexError In File <in>

Read the errors carefully

and the compiler will teach you itself

Let’s Write Some Rust Javascript


function hello(name) {
    return "hello " + name
}
def hello():
    return "Hello World"

print(hello())
fn hello(name) {
    return "hello " + name
}
error: expected one of `:`, `@`, or `|`, found `)`
 --> main.rs:1:14
1 | fn hello(name) {
  |              ^ expected one of `:`, `@`, or `|`
  |
help: if this is a `self` type, give it a parameter name
  |
1 | fn hello(self: name) {
  |          +++++
help: if this is a parameter name, give it a type
  |
1 | fn hello(name: TypeName) {
  |              ++++++++++
help: if this is a type, explicitly ignore the parameter
  |
1 | fn hello(_: name) {
  |          ++
help: if this is a parameter name, give it a type
  |
1 | fn hello(name: TypeName) {
  |              ++++++++++
fn hello(name: string) {
    return "hello " + name
}
error[E0412]: cannot find type `string` in this scope
 --> main.rs:1:16
  |
1 | fn hello(name: string) {
  |                ^^^^^^ help: a struct with a similar
    name exists (notice the capitalization): `String`
fn hello(name: String) {
    return "hello " + name
}
error[E0369]: cannot add `String` to `&str`
 --> main.rs:2:21
  |
2 |    return "hello " + name
  |           -------- ^ ---- String
  |           |        |
  |           |       `+` cannot be used to concatenate
              |       a `&str` with a `String`
  |           &str
  |
help: create an owned `String` on the left
      and add a borrow on the right
  |
2 |     return "hello ".to_owned() + &name
  |                   +++++++++++   +
error[E0369]: cannot add `String` to `&str`
 --> main.rs:2:21
  |
2 |    return "hello " + name
  |           -------- ^ ---- String
  |           |        |
  |           |       `+` cannot be used to concatenate
              |       a `&str` with a `String`
  |           &str
  |
help: create an owned `String` on the left
      and add a borrow on the right
  |
2 |     return "hello ".to_owned() + &name
  |                   +++++++++++   +
error[E0369]: cannot add `String` to `&str`
 --> main.rs:2:21
  |
2 |    return "hello " + name
  |           -------- ^ ---- String
  |           |        |
  |           |       `+` cannot be used to concatenate
              |       a `&str` with a `String`
  |           &str
  |
help: create an owned `String` on the left
      and add a borrow on the right
  |
2 |     return "hello ".to_owned() + &name
  |                   +++++++++++   +
error[E0369]: cannot add `String` to `&str`
 --> main.rs:2:21
  |
2 |    return "hello " + name
  |           -------- ^ ---- String
  |           |        |
  |           |       `+` cannot be used to concatenate
              |       a `&str` with a `String`
  |           &str
  |
help: create an owned `String` on the left
      and add a borrow on the right
  |
2 |     return "hello ".to_owned() + &name
  |                   +++++++++++   +
error[E0369]: cannot add `String` to `&str`
 --> main.rs:2:21
  |
2 |    return "hello " + name
  |           -------- ^ ---- String
  |           |        |
  |           |       `+` cannot be used to concatenate
              |       a `&str` with a `String`
  |           &str
  |
help: create an owned `String` on the left
      and add a borrow on the right
  |
2 |     return "hello ".to_owned() + &name
  |                   +++++++++++   +
error[E0369]: cannot add `String` to `&str`
 --> main.rs:2:21
  |
2 |    return "hello " + name
  |           -------- ^ ---- String
  |           |        |
  |           |       `+` cannot be used to concatenate
              |       a `&str` with a `String`
  |           &str
  |
help: create an owned `String` on the left
      and add a borrow on the right
  |
2 |     return "hello ".to_owned() + &name
  |                   +++++++++++   +
fn hello(name: String) {
    return "hello ".to_owned() + &name
}
error[E0308]: mismatched types
 --> main.rs:2:12
  |
1 | fn hello(name: String) {
  |            - help: try adding a return type: `-> String`
2 |     return "hello ".to_owned() + &name
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`,
                found `String`
For more information about an error, try `rustc
--explain E0308`.

Let’s try running rustc --explain E0308

Expected Type Did Not Match the Received Type

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

plus_one("Not a number");
//       ^^^^^^^^^^^^^^ expected `i32`, found `&str`

if "Not a bool" {
// ^^^^^^^^^^^^ expected `bool`, found `&str`
}

let x: f32 = "Not a float";
//     ---   ^^^^^^^^^^^^^ expected `f32`, found `&str`
//     |
//     expected due to this
fn hello(name: String) -> String {
    return "hello ".to_owned() + &name
}


$ cargo build
Finished dev [unoptimized + debuginfo] target(s) in 0.01s

The Compiler Teaches You

https://rustup.rs

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Beautiful one-liner install

  • except if you are running Windows

Updating Rust

rustup update

Local Offline Docs

rustup doc

Or directly open the Rust Book

rustup doc --book

Hello World


fn main() {
    println!("Hello, world!");
}

Compiling Your Code


rustc main.rs

Creating a Project with Cargo


$ cargo new hello
$ cd hello


Three items have been created for you

  • Cargo.toml
  • src/main.rs
  • local git repo

Cargo.toml


[package]
name = "hello"
version = "0.1.0"
edition = "2021"

[dependencies]

Building Your Project


$ cargo clean           # remove all previous builds
$ cargo build           # build the project incrementally
$ cargo build --release # build release binaries

Running Your Project


$ cargo run # run your code in debug mode

Checking Your Project


$ cargo check # check for errors

Cargo: Swiss Army Knife


cargo doc                 # local package documentation
cargo bench               # built-in benchmarking
cargo test                # built-in parallel testing
cargo add aws-sdk         # easily add dependencies
cargo install             # install exes into .cargo/bin
cargo clippy              # run the code linter
cargo publish             # publish packages to crates.io

Modern Tooling

  • cargo - packaging, building
  • cargo fmt - standard formatting
  • cargo test - doc and unit tests
  • cargo bench - benchmarking
  • cargo clippy - code linting
  • rustup - rust version switching

https://github.com/rust-lang/rustlings

Additional Resources

Rust

Your code can be perfect

Our First Rust Project

The Guessing Game

use std::io;

fn main() {
    println!("Guess the number!");
    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {}", guess);
}
use std::io;

fn main() {
    println!("Guess the number!");
    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {}", guess);
}

Generating a Secret Number


Cargo.toml

[dependencies]
rand = "0.8.5"

Updating a Crate


$ cargo update
    Updating crates.io index
    Updating rand v0.8.5 -> v0.8.6

Generating a Random Number


use std::io;
use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number =
        rand::thread_rng().gen_range(1..=100);

Consulting local docs

cargo doc --open

Comparing the Guess to the Secret Number


use std::cmp::Ordering;

fn main() {
    // --snip--
    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}

Resolving the Compile-Time Error


let mut guess = String::new();

io::stdin()
    .read_line(&mut guess)
    .expect("Failed to read line");

let guess: u32 = guess.trim().parse().expect("Please type
a number!");

Adding a Loop


    // --snip--
    println!("The secret number is: {secret_number}");

    loop {
        println!("Please input your guess.");

        // --snip--
    }

Quitting after a Correct Guess


        // --snip--
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }

Handling Invalid Input


        // --snip--
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {guess}");

You have just built your first Rust project!

Required Additional Reading

The Rust Programming Language, Chapter 1-2