Rust

Rust began as a personal project of Graydon Hoare in 2006, was sponsored by Mozilla, and reached version 1.0 in 2015. It aims at the speed and control of C and C++ while making memory errors impossible in safe code, with no garbage collector.

Paradigm: Systems Multi-Paradigm Languages

What Makes Rust Special

  • Ownership. Every value has one owner. When the owner goes out of scope the value is freed, and assigning it elsewhere moves it. The compiler checks this, so use-after-free and double-free cannot happen.
  • Borrowing and lifetimes. You can lend a value as &T (shared, read-only) or &mut T (exclusive, writable), never both at once. This rule also prevents data races between threads.
  • Traits instead of inheritance. A trait describes behaviour a type can have. Types implement traits, and generic code asks for them as bounds. There are no classes and no inheritance of data.
  • Enums and pattern matching. An enum can carry data in each variant (an algebraic data type), and match must cover every case. Option and Result replace null and exceptions.
  • Functional idioms. Closures and iterator chains (map, filter, sum) compile down to loops with no overhead. Values are immutable unless declared mut.
  • Fearless concurrency. The same ownership rules are checked across threads, so many races become compile errors.

Example: Enums, traits, iterators and ownership

use std::f64::consts::PI;

#[derive(Debug)]
enum Shape {
    Circle(f64),
    Rect { w: f64, h: f64 },
}

trait Area {
    fn area(&self) -> f64;
}

impl Area for Shape {
    fn area(&self) -> f64 {
        match self {
            Shape::Circle(r) => PI * r * r,
            Shape::Rect { w, h } => w * h,
        }
    }
}

fn main() {
    let shapes = vec![Shape::Circle(1.0), Shape::Rect { w: 2.0, h: 3.0 }];

    // functional: an iterator chain
    let total: f64 = shapes.iter().map(|s| s.area()).sum();
    println!("{:.2}", total);                       // 9.14

    // Option instead of null
    match shapes.iter().find(|s| s.area() > 5.0) {
        Some(s) => println!("{:?}", s),              // Rect { w: 2.0, h: 3.0 }
        None => println!("none"),
    }

    // ownership: a move
    let name = String::from("rust");
    let moved = name;
    // println!("{}", name);   // error: borrow of moved value
    println!("{}", moved);                          // rust
}

How It Works

  • Shape is an enum whose variants hold different data. match in area handles each variant, and omitting one would be a compile error.
  • Area is a trait, and impl Area for Shape attaches the behaviour. Any generic function can now accept T: Area.
  • shapes.iter().map(...).sum() is an iterator pipeline. It is compiled into a plain loop with no allocation.
  • find returns Option, so the code must handle the case where nothing matches.
  • let moved = name moves the string. Using name afterwards is rejected by the compiler, which is how Rust avoids double frees.

History and Where It Is Used

Rust is used for browsers (parts of Firefox), operating system components, command-line tools, embedded systems, WebAssembly, network services and cryptography. Support for Rust drivers was added to the Linux kernel in version 6.1. The toolchain includes cargo, a build tool and package manager, and the crates.io registry. The main cost is a steeper learning curve while you learn to satisfy the borrow checker. See also the full Rust roadmap on this site.

Learn More