Visit me elsewhere:

Rust Cheatsheet

Simple program

main.rs

    fn main() {
        println!("test");
    }

Naming

Use underscores to separate words; hello_world.rs.

Cargo project

Cargo is rusts native build and dependency management tool and defines a project folder structure

  • src Source and unit test files
  • target
  • tests Integration tests
  • Cargo.toml
  • Cargo.lock Generated file; concrete dependencies (e.g. concrete git revision) Should be versioned for executable projects (not cargo libraries)
    $ cargo new projectname --bin
    $ cargo build
    $ cargo build --release
    $ cargo run
    $ cargo build
    $ cargo doc --open

Functions and Macros

    println!("test"); // Macro call
    println("test"); // Function call
String::new()

Associated function (in other langauges called static method)

    use std::io;

Variables - Mutability, Shadowing

    // Variables are immutable by default
    let x = 2;
    // Compiler error:
    //x = 3;

    let mut y = 3;
    y = 5;

    let y = y + 3;
    println!("Shadowed y: {}", y)

    // Constants - always immutable and type annotated
    const MAX_POINTS: u32 = 100_000;
    println!("Constant MAX_POINTS: {}", MAX_POINTS);

Types

Scalar

Integer Types

Length Signed Unsigned Range
8-bit i8 u8 2^8 = 256 = 0xFF = (s) -128;127
16-bit i16 u16 2^16 = 65,536 = 0xFFFF = (s)
32-bit i32 u32 2^32 = 4,294,967,296 = 0xFFFF_FFFF
64-bit i64 u64 2^64 = 18,446,744,073,709,551,616 = 0xFFFF_FFFF_FFFF_FFFF
arch isize usize

Integer Literals

Number literals Example
Decimal 98_222
Hex 0xff
Octal 0o77
Binary 0b1111_0000
Byte (u8 only) b’A’

Floating-point Types

    let x: f64 = 2.0;
    let x: f32 = 2.0;
    // Implicitly f64
    let x = 2.0;

Numeric Operations

    // addition
    let sum = 5 + 10;
    // subtraction
    let difference = 95.5 - 4.3;
    // multiplication
    let product = 4 * 30;
    // division
    let quotient = 56.7 / 32.2;
    // remainder
    let remainder = 43 % 5;

Boolean

    let t = true;
    let f: bool = false;

Character

    let c = 'z';
    let z: char = 'ℤ';
    let heart_eyed_cat = '😻';

Compound Types

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

Tuple

A tuple is a general way of grouping together some number of other values with a variety of types into one compound type.

    let tup_explicit: (i32, f64, u8) = (500, 6.4, 1);
    let tup = (500, 6.4, 1);
    let (x, y, z) = tup;
    println!("The value of y is: {}", y);

Array

Unlike a tuple, every element of an array must have the same type.

Arrays in Rust are different than arrays in some other languages because arrays in Rust have a fixed length: once declared, they cannot grow or shrink in size.

Arrays are useful when you want your data allocated on the stack rather than the heap.

    let a = [1, 2, 3, 4, 5];
    let first = a[0];
    let second = a[1];
    // Accessing invalid index produces a panic
    //let element = a[index];

Functions

    fn main() {
        another_function(5, 6);
    }

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

Statements and Expressions

    // Statement
    let x = 5;
    //      ^ Expression "5"

    let y = {
        let x = 3;
        x + 1
    };
    // Expression {} with last line without a semicolon

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

Return Values

    fn five() -> i32 {
        5
    }

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

Comments

    // Line comments

Control Flow

if

    if 2 < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }

if is an expression

    let condition = true;
    let number = if condition {
        5
    } else {
        6
    };

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

Loops

    loop {
        println!("again!");
    }
    while (true) {
    }

    let a = [10, 20, 30, 40, 50];
    for element in a.iter() {
        println!("the value is: {}", element);
    }

    for number in (1..4).rev() {
        println!("{}!", number);
    }
    println!("LIFTOFF!!!");

Ownership

Stack: push and pop; last in, first out; fixed size at compile time

Heap: memory is allocated and freed at runtime

  • Each value in Rust has a variable that’s called its owner.
  • There can only be one owner at a time.
  • When the owner goes out of scope, the value will be dropped.
    {                      // s is not valid here, it’s not yet declared
        let s = "hello";   // s is valid from this point forward
        // do stuff with s
    }                      // this scope is now over, and s is no longer valid
    // Immutable string literal; stack
    let a = "A string literal";
    // Heap allocated
    let b = String::from("hello");
    b.push_str(", world!");

Move

    let s1 = String::from("hello");
    let s2 = s1;
    // Invalid; Compiler error
    //println!("{}", s1);

Clone

    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("s1 = {}, s2 = {}", s1, s2);

Stack only data Copy

Stack data is always copied.

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

Traits: Copy vs Drop

A type can only have either the Copy or the Drop trait.

Copy:

  • All the integer types, like u32.
  • The boolean type, bool, with values true and false.
  • All the floating point types, like f64.
  • Tuples, but only if they contain types that are also Copy. (i32, i32) is Copy, but (i32, String) is not.

Functions

    let s = String::from("hello");
    takes_ownership(s);
    let x = 5;
    makes_copy(x);

Return Values

References

    fn main() {
        let s1 = String::from("hello");
        let len = calculate_length(&s1);
        println!("The length of '{}' is {}.", s1, len);
    }
    // Borrowing per reference (immutable by default)
    fn calculate_length(s: &String) -> usize {
        s.len()
    }
    fn main() {
        let mut s = String::from("hello");
        change(&mut s);
    }
    fn change(some_string: &mut String) {
        some_string.push_str(", world");
    }

Only one mutable reference can exist in a particular scope. Prevents data races. (Also can not create a mutable reference in the scope of an immutable reference.)

    let mut s = String::from("hello");
    let r1 = &mut s;
    // Will fail with compiler error
    //let r2 = &mut s;

Dangling References

Prevented by the compiler.

Rules of References

  • At any given time, you can have either but not both of:
  • * One mutable reference.
  • * Any number of immutable references.
  • References must always be valid.

Slices

Determining and holding index values could become invalid on content changes. Slices keep references to owned values. (Compiler guaranteed validity.)

    let s = String::from("hello world");
    let hello = &s[0..5];
    // Equal, not specifying start index 0
    let hello = &s[..2];
    let world = &s[6..11];
    let world = &s[6..len];
    let world = &s[6..];
    // Entire string
    let slice = &s[0..len];
    let slice = &s[..];
    // Will produce a compiler error.
    //s.clear();

Inclusive begin, exclusive end index.

String literals are slices

    let s: &str = "Hello, world!";

Other Slices

    let a = [1, 2, 3, 4, 5];
    let slice: &[i32] = &a[1..3];

Structs

    struct User {
        username: String,
        email: String,
        sign_in_count: u64,
        active: bool,
    }
    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
    println!("{}", user1.email);
    user2.email = String::from("someone-else@example.com");

field init shorthand

If you have variables with the same names as struct fields, you can use field init shorthand.

    fn build_user(email: String, username: String) -> User {
        User {
            email,
            username,
            active: true,
            sign_in_count: 1,
        }
    }

New Struct with Update

    let user2 = User {
        email: String::from("another@example.com"),
        username: String::from("anotherusername567"),
        ..user1
    };

Tuple Structs

  • Tuple: unnamed type and unnamed values (by order)
  • Struct: Named type and named fields
  • Tuple struct: Named type, unnamed fields (type checkability)
    struct Color(i32, i32, i32);
    struct Point(i32, i32, i32);
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);

Unit-Like Structs Without Any Fields

The unit type: ()

Ownership of Struct data

For a struct to be able to hold a reference to data it does not own, lifetimes are necessary. [later]

Debug trait

    #[derive(Debug)]
    struct Rectangle {
        length: u32,
        width: u32,
    }

    // Fails without the Display trait
    // println!("rect1 is {}", rect1);
    // Fails without the Debug trait;
    // can be automatically added via #[derive(Debug)]
    println!("rect1 is {:?}", rect1);
    // Expant debug format
    println!("rect1 is {:#?}", rect1);

Methods

    struct Rectangle {
        length: u32,
        width: u32,
    }
    impl Rectangle {
        fn area(&self) -> u32 {
            self.length * self.width
        }
        fn can_hold(&self, other: &Rectangle) -> bool {
            self.length > other.length && self.width > other.width
        }
        // Associated function
        fn square(size: u32) -> Rectangle {
            Rectangle { length: size, width: size }
        }
    }
    fn main() {
        let rect1 = Rectangle { length: 50, width: 30 };
        println!("{}", rect1.area());

        let rect1 = Rectangle { length: 50, width: 30 };
        let rect2 = Rectangle { length: 40, width: 10 };
        let rect3 = Rectangle { length: 45, width: 60 };
        println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
        println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));

        let sq = Rectangle::square(3);
    }

Enums

    enum IpAddrKind {
        V4,
        V6,
    }
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;
    fn route(ip_type: IpAddrKind) { }
    route(IpAddrKind::V4);
    route(IpAddrKind::V6);

V4 and V6 are “variants” of the enum IpAddrKind

Associated values:

    enum IpAddr {
        V4(String),
        V6(String),
    }
    let home = IpAddr::V4(String::from("127.0.0.1"));
    let loopback = IpAddr::V6(String::from("::1"));
    enum IpAddr {
        V4(u8, u8, u8, u8),
        V6(String),
    }
    let home = IpAddr::V4(127, 0, 0, 1);
    let loopback = IpAddr::V6(String::from("::1"));
    struct Ipv4Addr {
    }
    struct Ipv6Addr {
    }
    enum IpAddr {
        V4(Ipv4Addr),
        V6(Ipv6Addr),
    }

enum IpAddr

    enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
        ChangeColor(i32, i32, i32),
    }
    impl Message {
        fn call(&self) {
            // method body would be defined here
        }
    }
    let m = Message::Write(String::from("hello"));
    m.call();

Option enum

Option
Included in the prelude.
Some() and None can be used without qualification.

    enum Option<T> {
        Some(T),
        None,
    }
    let some_number = Some(5);
    let some_string = Some("a string");
    let absent_number: Option<i32> = None;

match

    enum Coin {
        Penny,
        Nickel,
        Dime,
        Quarter,
    }
    fn value_in_cents(coin: Coin) -> i32 {
        match coin {
            Coin::Penny => 1,
            Coin::Nickel => 5,
            Coin::Dime => 10,
            Coin::Quarter => 25,
        }
    }
    fn value_in_cents_commented(coin: Coin) -> i32 {
        match coin {
            Coin::Penny => {
                println!("Lucky penny!");
                1
            },
            Coin::Nickel => 5,
            Coin::Dime => 10,
            Coin::Quarter => 25,
        }
    }
    #[derive(Debug)] // So we can inspect the state in a minute
    enum UsState {
        Alabama,
        Alaska,
        // ... etc
    }
    enum Coin {
        Penny,
        Nickel,
        Dime,
        Quarter(UsState),
    }
    fn value_in_cents(coin: Coin) -> i32 {
        match coin {
            Coin::Penny => 1,
            Coin::Nickel => 5,
            Coin::Dime => 10,
            Coin::Quarter(state) => {
                println!("State quarter from {:?}!", state);
                25
            },
        }
    }
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            // i binds to the value contained in Some
            Some(i) => Some(i + 1),
        }
    }
    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);

Not catching all cases in a match is a compiler error.

The _ placeholder

We only care about the values 1, 3, 5, 7 of an u8 integer:

    let some_u8_value = 0u8;
    match some_u8_value {
        1 => println!("one"),
        3 => println!("three"),
        5 => println!("five"),
        7 => println!("seven"),
        _ => (),
    }

if let

A shorthand for a match statement with only one arm.

    if let Some(3) = some_u8_value {
        println!("three");
    }

With else case:

    let mut count_no_quarter = 0;
    if let Coin::Quarter(state) = coin {
        println!("State quarter from {:?}!", state);
    } else {
        count_no_quarter += 1;
    }

Modules

Units for code structuring and reuse.

  • The mod keyword declares a new module. Code within the module appears either immediately following this declaration within curly braces or in another file.
  • By default, functions, types, constants, and modules are private. The pub keyword makes an item public and therefore visible outside its namespace.
  • The use keyword brings modules, or the definitions inside modules, into scope so it’s easier to refer to them.
    mod network {
        // Use with network::connect
        fn connect() {
        }
    }

    mod client {
        // Use with client::connect
        fn connect() {
        }
    }
    mod network {
        fn connect() {
        }

        mod client {
            fn connect() {
            }
        }
    }
    ├── src
    │   ├── client.rs
    │   ├── lib.rs
    │   └── network
    │       ├── mod.rs
    │       └── server.rs
    mod outermost {
        pub fn middle_function() {}

        fn middle_secret_function() {}

        mod inside {
            pub fn inner_function() {}

            fn secret_function() {}
        }
    }

    fn try_me() {
        outermost::middle_function();
        // Fails
        outermost::middle_secret_function();
        // Fails
        outermost::inside::inner_function();
        // Fails
        outermost::inside::secret_function();
    }

use nested module

    pub mod a {
        pub mod series {
            pub mod of {
                pub fn nested_modules() {}
            }
        }
    }

    use a::series::of;

    fn main() {
        of::nested_modules();
    }

use function

    pub mod a {
        pub mod series {
            pub mod of {
                pub fn nested_modules() {}
            }
        }
    }

    use a::series::of::nested_modules;

    fn main() {
        nested_modules();
    }

use multiple enum values in one statement

    enum TrafficLight {
        Red,
        Yellow,
        Green,
    }

    use TrafficLight::{Red, Yellow};

    fn main() {
        let red = Red;
        let yellow = Yellow;
        let green = TrafficLight::Green;
    }

* glob

    enum TrafficLight {
        Red,
        Yellow,
        Green,
    }

    use TrafficLight::*;

    fn main() {
        let red = Red;
        let yellow = Yellow;
        let green = Green;
    }

Collections

Vectors Vec<T>

    let v: Vec&gt;i32&lt; = Vec::new();
    let v = vec![1, 2, 3];
    let mut v = Vec::new();
    v.push(5);
    v.push(6);
    v.push(7);
    v.push(8);

Dropping a Vector Drops its Elements (dropped when going out of scope).

    let v = vec![1, 2, 3, 4, 5];
    // Returns the element (or panics if not existant)
    let third: &i32 = &v[2];
    // Returns an Option<T>
    let third: Option<&i32> = v.get(2);
    <enum SpreadsheetCell {
        Int(i32),
        Float(f64),
        Text(String),
    }

    let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];

Strings

The Display trait implements to_string().

    let a = "initial content".to_string();
    let b = String::from("initial content");
    let mut s = String::from("foo");
    s.push_str("bar");
    s.push('l');
    let s1 = String::from("Hello, ");
    let s2 = String::from("world!");
    // Note that s1 has been moved here and can no longer be used
    // Operator + uses add method,
    // compiler makes use of deref coercion
    // to coerce s2 String reference into a string slice &s2[..]
    let s3 = s1 + &s2;

format! macro

    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");
    let s = format!("{}-{}-{}", s1, s2, s3);

Indexing, Unicode, byte, scalar, grapheme cluster

Hindi word “नमस्ते” written in the Devanagari script, it is ultimately stored as a Vec of u8 values that looks like this:
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164, 224, 165, 135]

Unicode scalar values (rusts char type): ['न', 'म', 'स', '्', 'त', 'े']
but the fourth and sixth are not letters, they’re diacritics that don’t make sense on their own

grapheme clusters (“letters”): ["न", "म", "स्", "ते"]

=> Not possible to index a value from a String.

    // In this particular string every character is two bytes.
    let hello = "Здравствуйте";
    let s = &hello[0..4];
    // Will panic at runtime for invalid index.
    &hello[0..1]
    for c in "नमस्ते".chars() {
        println!("{}", c);
    }
    for b in "नमस्ते".bytes() {
        println!("{}", b);
    }

Working with grapheme clusters on Strings is not covered by the standard library.

Refer to external crates.

HashMap

    use std::collections::HashMap;
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

For Copy trait values are copied, for owned values like String, they are moved.

collect

    use std::collections::HashMap;
    let teams  = vec![String::from("Blue"), String::from("Yellow")];
    let initial_scores = vec![10, 50];
    // zip creates a vector of tuples (first, second)
    let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();

Get

    let team_name = String::from("Blue");
    let score = scores.get(&team_name);
    for (key, value) in &scores {
        println!("{}: {}", key, value);
    }

Updating

    use std::collections::HashMap;
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);
    // Overwrite
    scores.insert(String::from("Blue"), 25);
    println!("{:?}", scores);
    use std::collections::HashMap;
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);
    scores.entry(String::from("Yellow")).or_insert(50);
    scores.entry(String::from("Blue")).or_insert(50);
    println!("{:?}", scores);
    use std::collections::HashMap;
    let text = "hello world wonderful world";
    let mut map = HashMap::new();
    for word in text.split_whitespace() {
        let count = map.entry(word).or_insert(0);
        // Dereference value and add 1 to it
        *count += 1;
    }
    println!("{:?}", map);

Error Handling

Cargo.toml configuration to abort on panic (without stack unwinding):

    [profile.release]
    panic = 'abort'
    enum Result<T, E> {
        Ok(T),
        Err(E),
    }
    use std::fs::File;

    fn main() {
        let f = File::open("hello.txt");

        let f = match f {
            Ok(file) => file,
            Err(error) => {
                panic!("There was a problem opening the file: {:?}", error)
            },
        };
    }
    use std::fs::File;
    use std::io::ErrorKind;

    fn main() {
        let f = File::open("hello.txt");

        let f = match f {
            Ok(file) => file,
            // this if expression is a "match guard"
            // ref so that the error is not moved into the guard condition
            Err(ref error) if error.kind() == ErrorKind::NotFound => {
                match File::create("hello.txt") {
                    Ok(fc) => fc,
                    Err(e) => {
                        panic!(
                            "Tried to create file but there was a problem: {:?}",
                            e
                        )
                    },
                }
            },
            Err(error) => {
                panic!(
                    "There was a problem opening the file: {:?}",
                    error
                )
            },
        };
    }

In short, in the context of a pattern, & matches a reference and gives us its value, but ref matches a value and gives us a reference to it.

unwrap() unwraps the value from Result (Ok) or panics on error (Err variant).

let f = std::fs::File::open("hello.txt").expect("Failed to open hello.txt");
expect()

unwraps with a panic error text for the Err variant.

Propagating the error: return Err(e)

    use std::io;
    use std::io::Read;
    use std::fs::File;

    fn read_username_from_file() -> Result<String, io::Error> {
        let f = File::open("hello.txt");

        let mut f = match f {
            Ok(file) => file,
            Err(e) => return Err(e),
        };

        let mut s = String::new();

        match f.read_to_string(&mut s) {
            Ok(_) => Ok(s),
            Err(e) => Err(e),
        }
    }

Shorthand: ?

    use std::io;
    use std::io::Read;
    use std::fs::File;
    fn read_username_from_file() -> Result<String, io::Error> {
        // Return Err early or the value on Ok
        let mut f = File::open("hello.txt")?;
        let mut s = String::new();
        // Return Err early or the value on Ok
        f.read_to_string(&mut s)?;
        Ok(s)
    }

Even shorter (chaining after ?):

    fn read_username_from_file() -> Result<String, io::Error> {
        let mut s = String::new();
        File::open("hello.txt")?.read_to_string(&mut s)?;
        Ok(s)
    }

? can only be used in functions that return a Result

When prototyping unwrap and expect are useful, until you polish it with error handling.

If a method call fails in a test, we’d want the whole test to fail, even if that method isn’t the functionality under test. Because panic! is how a test gets marked as a failure, calling unwrap or expect is exactly what makes sense to do.

E.g. if we can verify it never Errs:
let home = "127.0.0.1".parse::<IpAddr>().unwrap();

To panic makes sense if the program is in a bad state:
The bad state is not something that’s expected to happen occasionally
Your code after this point needs to rely on not being in this bad state
There’s not a good way to encode this information in the types you use

Panic when the contract is violated.

Generics, Traits, Lifetimes

    fn largest<T>(list: &[T]) -> T {
    struct Point<T> {
        x: T,
        y: T,
    }
    let wont_work = Point { x: 5, y: 4.0 };
    enum Option<T> {
        Some(T),
        None,
    }

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

Generic methods

    struct Point<T> {
        x: T,
        y: T,
    }
    impl<T> Point<T> {
        fn x(&self) -> &T {
            &self.x
        }
    }
    fn main() {
        let p = Point { x: 5, y: 10 };

        println!("p.x = {}", p.x());
    }
struct Point<T, U> {
        x: T,
        y: U,
    }
    impl<T, U> Point<T, U> {
        fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
            Point {
                x: self.x,
                y: other.y,
            }
        }
    }

Traits

Traits abstract over behavior that types can have in common.

    pub trait Summarizable {
        fn summary(&self) -> String;
    }

Implementation of a trait:

    pub struct NewsArticle {
        pub headline: String,
        pub location: String,
        pub author: String,
        pub content: String,
    }

    impl Summarizable for NewsArticle {
        fn summary(&self) -> String {
            format!("{}, by {} ({})", self.headline, self.author, self.location)
        }
    }

    pub struct Tweet {
        pub username: String,
        pub content: String,
        pub reply: bool,
        pub retweet: bool,
    }

    impl Summarizable for Tweet {
        fn summary(&self) -> String {
            format!("{}: {}", self.username, self.content)
        }
    }

Orphan rule: we may implement a trait on a type as long as either the trait or the type are local to our crate.

Default implementation

    pub trait Summarizable {
        fn summary(&self) -> String {
            String::from("(Read more...)")
        }
    }
    // Use default trait method implementation
    impl Summarizable for NewsArticle {}

Trait methods can call other trait methods.

Trait bounds

    pub fn notify<T: Summarizable>(item: T) {
        println!("Breaking news! {}", item.summary());
    }

Multiple traits with +

    fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {

Or with a where clause:

    fn some_function<T, U>(t: T, u: U) -> i32
        where T: Display + Clone,
            U: Clone + Debug
    {

Lifetimes

    &i32        // a reference
    &'a i32     // a reference with an explicit lifetime
    &'a mut i32 // a mutable reference with an explicit lifetime
        longest<'a>(x: &'a str, y: &'a str) -> &'a str {
        if x.len() > y.len() {
            x
        } else {
            y
        }
    }

The lifetime of the returned reference depends on which reference (x or y) is chosen.
This is represented by the 'a lifetime.
The returned reference is valid as long as both the referenced x and y are valid.

For some lifetime ‘a, the function will get two parameters, both of which are string slices that live at least as long as the lifetime ‘a. The function will return a string slice that also will last at least as long as the lifetime ‘a.

lifetime syntax is about connecting the lifetimes of various arguments and return values of functions.

In a struct (struct with reference)

    struct ImportantExcerpt<'a> {
        part: &'a str,
    }
    fn main() {
        let novel = String::from("Call me Ishmael. Some years ago...");
        let first_sentence = novel.split('.')
            .next()
            .expect("Could not find a '.'");
        let i = ImportantExcerpt { part: first_sentence };
    }

Lifetime Elision

    // Simple, lifetime pattern recognized by compiler
    fn first_word(s: &str) -> &str {
    // Elaborative
    fn first_word<'a>(s: &'a str) -> &'a str {

Lifetimes on function or method parameters are called input lifetimes, and lifetimes on return values are called output lifetimes.

  • Each parameter that is a reference gets its own lifetime parameter. In other words, a function with one parameter gets one lifetime parameter: fn foo<‘a>(x: &‘a i32), a function with two arguments gets two separate lifetime parameters: fn foo<‘a, ‘b>(x: &‘a i32, y: &‘b i32), and so on.
  • If there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters: fn foo<‘a>(x: &‘a i32) -> &‘a i32.
  • If there are multiple input lifetime parameters, but one of them is &self or &mut self because this is a method, then the lifetime of self is assigned to all output lifetime parameters. This makes writing methods much nicer.
    impl<'a> ImportantExcerpt<'a> {
        fn level(&self) -> i32 {
            3
        }
    }
    impl<'a> ImportantExcerpt<'a> {
        fn announce_and_return_part(&self, announcement: &str) -> &str {
            println!("Attention please: {}", announcement);
            self.part
        }
    }

‘static lifetime

entire duration of the program

    let s: &'static str = "I have a static lifetime.";

Combination

    use std::fmt::Display;
    fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
        where T: Display
    {
        println!("Announcement! {}", ann);
        if x.len() > y.len() {
            x
        } else {
            y
        }
    }

Tests

test attribute annotation: #[test]

mod with #[cfg(test)]

    assert!(!smaller.can_hold(&larger));
    assert_eq!(4, add_two(2));
    assert_ne!(4, add_two(2));
    #[should_panic]
    #[should_panic(expected = "Guess value must be less than or equal to 100")]
    #[ignore]

    assert!(
        result.contains("Carol"),
        "Greeting did not contain name, value was `{}`", result
    );

When the assertions fail, these macros print their arguments using debug formatting, which means the values being compared must implement the PartialEq and Debug traits.

#[derive(PartialEq, Debug)]

    $ cargo test -- --test-threads=1
    $ cargo test -- --nocapture
    // Run only test one_hundred
    $ cargo test one_hundred
    // Run all tests with add in their name or module
    $ cargo test add
    $ cargo test -- --ignored
    // per file
    $ cargo test --test integration_test

tests/common/mod.rs for common logic for integration tests.

Binary projects will have little logic in src/main.rs, mostly calls stuff in src/lib.rs, which is thus testable.

Closures

    let expensive_closure = |num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    };
    expensive_closure(intensity)

Optional type annotations:

    let expensive_closure = |num: i32| -> i32 {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    };
    fn  add_one_v1   (x: i32) -> i32 { x + 1 }
    let add_one_v2 = |x: i32| -> i32 { x + 1 };
    let add_one_v3 = |x|             { x + 1 };
    let add_one_v4 = |x|               x + 1  ;
    let config = Config::new(&args).unwrap_or_else(|err| {
        println!("Problem parsing arguments: {}", err);
        process::exit(1);
    });

Traits Fn, FnMut, FnOnce

    struct Cacher<T>
        where T: Fn(i32) -> i32
    {
        calculation: T,
        value: Option<i32>,
    }
    impl<T> Cacher<T>
        where T: Fn(i32) -> i32
    {
        fn new(calculation: T) -> Cacher<T> {
            Cacher {
                calculation,
                value: None,
            }
        }

        fn value(&mut self, arg: i32) -> i32 {
            match self.value {
                Some(v) => v,
                None => {
                    //FIXME: Assumes arg will always be the same
                    let v = (self.calculation)(arg);
                    self.value = Some(v);
                    v
                },
            }
        }
    }

Captures Environment

    fn main() {
        let x = 4;
        let equal_to_x = |z| z == x;
        let y = 4;
        assert!(equal_to_x(y));
    }

Closures can capture values from their environment in three ways, which directly map to the three ways a function can take a parameter: taking ownership, borrowing immutably, and borrowing mutably. These ways of capturing values are encoded in the three Fn traits as follows:

  • FnOnce takes ownership of the variables it captures from the environment and moves those variables into the closure when the closure is defined. Therefore, a FnOnce closure cannot be called more than once in the same context.
  • Fn borrows values from the environment immutably.
  • FnMut can change the environment since it mutably borrows values.

If we want to force the closure to take ownership of the values it uses in the environment, we can use the move keyword before the parameter list. This is mostly useful when passing a closure to a new thread in order to move the data to be owned by the new thread.

    let x = vec![1, 2, 3];
    let equal_to_x = move |z| z == x;