TRust The Process

Intro to Rust

Meir Kriheli

Rust is a multi-paradigm system programming language focused on safety, especially safe concurrency.

Rust is syntactically similar to C++, but is designed to provide better memory safety while maintaining high performance.

Wikipedia

Most loved programming language in the Stack Overflow Developer Survey since 2016

One of the fastest in Techempower benchmarks

History

  • 2006: Personal project of Graydon Hoare from Mozilla.
  • 2009: Sponsored by Mozilla
  • 2010: Announced
  • 2012: Servo started
  • 2015: Rust 1.0 (aka 2015)
  • 2018: Rust 2018
  • 2020: Foundation, after Mozilla fires team

Likes

Tooling, no GC, high level with zero cost abstractions, safety, error handling, pattern matching, compile error messages

Dislikes

Compile times, initial complexity

Cargo

  • Package manager (download, create, upload)
  • crates.io
  • Build tool
  • Documentation
  • Testing, benchmark

Cargo

Extensible via cargo-xxxx subcommands (like git), notable:

  • clippy - Linting
  • audit - check for crates with security vulnerabilities
  • fmt - Formatting according to the style guide

Basic Syntax and Tooling

Start new project


            cargo init "project name"
            ...
            cargo run
            

            cargo init --lib "library name"
            

Hello World


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

Variables (let)


                fn main() {
                  let x = 7; // type inference
                  let y: i8 = 100;
                  let z = 6u32;
                  println!("x, y, z:  {}, {}, {}", x, y, z);
                }
              
x, y, z:  7, 100, 6

Immutable by default


              fn main() {
                  let _x = 6;
                  _x = 4;
              }
            

error[E0384]: cannot assign twice to immutable variable `_x`
 --> src/main.rs:3:5
  |
2 |     let _x = 6;
  |         --
  |         |
  |         first assignment to `_x`
  |         help: make this binding mutable: `mut _x`
3 |     _x = 4;
  |     ^^^^^^ cannot assign twice to immutable variable
            

Note the helpful message


              fn main() {
                  let mut x = 4; // type inference
                  x = if x < 3 { 0 } else { x + 1 };
                  println!("x is {}", x);
              }
            
x is 5

Ownership and Borrowing

Basis for compile time memory management and safety


fn main() {
    let name = String::from("Meir");
    greet(name);
}

fn greet(name: String) {
    println!("Hello {}", name);  // freed
}
            
Hello Meir

Moved value


fn main() {
    let name = String::from("Meir");
    greet(name);
    println!("{} was greeted!", name);
}

fn greet(name: String) {
    println!("Hello {}", name);
}
            

error[E0382]: borrow of moved value: `name`
 --> src/main.rs:4:33
  |
2 |     let name = String::from("Meir");
  |         ---- move occurs because `name` has type `std::string::String`, which does not implement the `Copy` trait
3 |     greet(name);
  |           ---- value moved here
4 |     println!("{} was greeted!", name);
  |                                 ^^^^ value borrowed here after move
            

Borrow


fn main() {
    let name = String::from("Meir");
    greet(&name);
    println!("{} was greeted!", name);
}

fn greet(name: &String) {
    println!("Hello {}", name);
}
            

            Hello Meir
            Meir was greeted!

Mutable Borrow

Borrows can't mutate values by default


fn main() {
    let mut name = String::from("Meir");
    add_greeting(&name);
    println!("{}!", name);
}

fn add_greeting(name: &String) {
    name.insert_str(0, "Welcome ");
}
            

error[E0596]: cannot borrow `*name` as mutable, as it is behind a `&` reference
 --> src/main.rs:7:5
  |
7 | fn add_greeting(name: &String) {
  |                       ------- help: consider changing this to be a mutable reference: `&mut std::string::String`
8 |     name.insert_str(0, "Welcome ");
  |     ^^^^ `name` is a `&` reference, so the data it refers to cannot be borrowed as mutable
            


fn main() {
    let mut name = String::from("Meir");
    add_greeting(&mut name);
    println!("{}!", name);
}

fn add_greeting(name: &mut String) {
    name.insert_str(0, "Welcome ");
}
            
Welcome Meir!

We can tell from the function signature and call that name is gonna mutate

Functions and blocks

Functions


fn sum(a: f64, b: f64) -> f64 {
    return a + b;
}
            

fn sum(a: f64, b: f64) -> f64 {
    a + b // last expression returns (no semicolon)
}
            

Blocks

Have their own scope, are also an expression

    let answer = {
        let y = 40;
        let x = 2;
        y + x
    };
    println!("The answer is {}", answer);
            

              The answer is 42
            

Pattern Matching, Enums

Simple to complex matches

Must satisfy all branches


fn main() {
    let age = 23;
    let description = match age {
        x if x < 13 => "child",
        13..=17 => "teen",
        18..=59 => "adult",
        x if x > 59 => "senior"
    };
    println!("{} is {}", age, description);
}
            

error[E0004]: non-exhaustive patterns: `std::i32::MIN..=12i32` and `60i32..=std::i32::MAX` not covered
 --> src/main.rs:3:29
  |
3 |     let description = match age {
  |                             ^^^ patterns `std::i32::MIN..=12i32` and `60i32..=std::i32::MAX` not covered
  |
  = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms

            

fn main() {
    let age = 23;
    let description = match age {
        x if x < 13 => "child",
        13..=17 => "teen",
        18..=59 => "adult",
        x if x > 59 => "senior",
        _ => "Unknown"
    };
    println!("{} is {}", age, description);
}
            

              23 is adult
            

Enums


#[allow(dead_code)]
enum TaskState {
    Running,
    Completed,
    Failed
}

fn main() {
    let state = TaskState::Completed;
    let message = match state {
        TaskState::Completed => "Completed",
        _ => "Not completed or failed",
    };
    println!("{}", message);
}
            

Not just numbers


#[allow(dead_code)]
enum Outcome {
    Hex(String),
    Number(i32),
}

fn main() {
    let a = Outcome::Hex("FE".into());
    let message = match a {
        Outcome::Hex(hex_str) => format!("Hex string of {}", hex_str),
        Outcome::Number(num) => format!("Number of {}", num),
    };
    println!("Outcome is {}", message);
}
            

              Outcome is Hex string of FE
            

In fact Option and Result are Enums as well


            enum Option<T> {
                None,
                Some(T),
            }
            

            enum Result<T, E> {
                Ok(T),
                Err(E),
            }
            

Structs

No OOP, uses implementation and Traits


#[derive(Debug)]
struct Point3D {
    x: f64,
    y: f64,
    z: f64,
}

fn main() {
    let p = Point3D {
        x: 10.0,
        y: 5.2,
        z: 6.5,
    };
    println!("Point in space: {:?}", p);
}
            
Point in space: Point3D { x: 10.0, y: 5.2, z: 6.5 }
            

#[derive(Debug)]
struct Point3D {
    x: f64,
    y: f64,
    z: f64,
}

impl Point3D {
    fn move_by(self, x: f64, y: f64, z: f64) -> Point3D {
        Point3D {
            x: self.x + x,
            y: self.y + y,
            z: self.z + z,
        }
    }
}
            


fn main() {
    let p = Point3D {
        x: 10.0,
        y: 5.2,
        z: 6.5,
    };
    let p2 = p.move_by(0.1, 0.2, 0.3);
    println!("Point in space: {:?}", p2);
}
            

Point in space: Point3D { x: 10.1, y: 5.4, z: 6.8 }
            

Traits

A trait is a collection of methods defined for an unknown type: Self. They can access other methods declared in the same trait. Traits can be implemented for any data type.

Traits - Rust By Example

Like an interface in other languages.

Traits are used all around, e.g. in stdlib: Iterator, Add, and many more.

Traits can be added to existing structs, this extending and adding functionality and features.

Let's look at Rayon which provides lightweight data parallelism for Rust.

Sum of squares


              fn sum_of_squares(input: &[i32]) -> i32 {
                  input.iter()
                       .map(|&i| i * i)
                       .sum()
              }
            

Parallel Sum of squares with Rayon


              use rayon::prelude::*;
              fn sum_of_squares(input: &[i32]) -> i32 {
                  input.par_iter() // <-- just change that!
                       .map(|&i| i * i)
                       .sum()
              }
            

Example: nth prime


fn upper_limit_for_nth_prime(n: u32)-> usize {
    if n < 6 {
        return 100
    }

    let n = n as f64;
    (n * (n.ln() + n.ln().ln())).ceil() as usize
}

/// Calculates nth prime (0 based) using 
/// [Sieve of Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes)
pub fn nth(n: u32) -> u32 {
    let limit = upper_limit_for_nth_prime(n) + 1;
    let mut is_prime = vec![true; limit];
    let mut total_found = 0;

    for number in 2..limit {
        if is_prime[number] {
            if total_found == n {
                return number as u32;
            }
            total_found += 1;
            for num_mul in (number * number..limit).step_by(number) {
                is_prime[num_mul] = false;
            }
        }
    }

    assert!(false, "Should not get here");
    0
}
            

Rust and Python

PyO3

PyO3 supports Python 3.6 and up, PyPy. Allows calling from Rust to Python and vice versa.


use pyo3::prelude::*;
use pyo3::wrap_pyfunction;

/// Formats the sum of two numbers as string.
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
    Ok((a + b).to_string())
}

/// A Python module implemented in Rust.
#[pymodule]
fn string_sum(py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;

    Ok(())
}
            

setuptools-rust

setuptools-rust is a plugin for setuptools to build Rust Python extensions implemented with PyO3 or rust-cpython.

Maturin

Maturin enables building and publishing crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages.

More to explore

resources

Thanks!

Questions?