dpway

The middle way!

[Rust][001] Rust Cheat Sheet

Tags = [ Rust ]

rust-cheat-sheet

A cheat sheet for the Rust programming language is a compact reference guide that provides quick access to essential syntax, concepts, and best practices for Rust developers. Rust is a systems programming language known for its focus on safety, concurrency, and performance. It features a unique ownership model that helps prevent memory leaks and data races, making it particularly suitable for concurrent programming.

Rust Cheat Sheet

1. Arrays & Vectors

  • Arrays have a fixed size - they can't grow or shrink
  • Makes a vector Vectors can change size
let activities = [
    "Reada book",
    "LearnRust",
    "Listent omusic",
    "Watcha movie",
    "Cooka meal"
]
let jobs = vec![
    "Software Engineer",
    "Cook"
]

2. Crates

  • Code is shared between projects using crates
  • Standard Library Crate: Included with every Rust project
  • External Crates: Written by other engineers, have to be installed into our project
use rand::{thread_rng, Rng};
// rand: Name of the crate (library) we are importing from
// thread_rng, Rng: Things we want to import

3. Struct

  • We want to store some data and attach some functionality to it. In Rust, a good tool for this is a struct
  • Structs are kind of like classes from other languages

If our app was more complex, and each thing different methods
Each thing has some different methods
=> Probably want to use structs

#[derive(Debug)] // Hey, compiler, automatically add all the 'Debug' functions to this struct
struct Desk {
    cards: Vec<String>
}

// 'Debug' functions
// Called the 'Debug' trait. Traits a set of functions
// **Imaginary extra functions added to our program by the compiler
fn nicely_print_a_deck(deck) {
    // Invalid syntax, added just for clarity
    return deck.cards.to_string();
}

// Creates an instance of a struct
let deck: Deck = Deck {cards: vec![]};
// vec![]: Creates an empty vector.

4. Inherent Implementations

  • Fancy term for 'add a function to a struct'
  • Used to define methods and associated functions
Associated FunctionsMethods
Use when you have functionality not tied to a specific instanceUse when you need to read or change fields on a specific instance
Examples: full_deck(), with_n_cards(10), empty_deck()Examples: shuffling cards, adding a card, removing a card, checking fi a card exists
called using the '::' syntaxcalled using the '.' syntax
impl Deck {
    // Associated Functions
    fn new(){
        //stuff...
    }
    // Methods
    // &self: Give me a read only reference to the deck
    // &mut self: Give me a read/write reference to the deck
    fn shuffle(&self) {
        //stuff...
    }
}
fn main() {
    let deck = Deck::new();
    deck.shuffle();
}

5. Modules

  • Code in all crates + programs is organized into modules
  • Every crate has a 'root' module and might have some additional submodules
  • We can create submodules in our own project to better organize our code
// We can directly access external crates
// To use internal modules we use the 'mod' keyword
mod games;

// 'use' pulls specific things into the scope of this file
// Import code from crates (packages) or other files in your project
// You can import multiple things on a single line using curly braces
use rand::{random};

fn main(){
    let rng = rand::thread_rng();
    let deck = games::Deck::new();
    let rand_num = random();
}

6. Enums

  • A set of values that are related together in some way

For our app, as described, each thing wil have very few methods
Every thing has the exact same set of methods
=> Probably want to use an enum

#[derive(Debug, PartialEq)]
enum DeckState{
    Initialized,
    Shuffled,
}

fn main() {
    let deck=Deck::new;
    if deck.state == DeckState::Initialized {
        println!("Deck not yet shuffled");
    } else if deck.state == DeckState::Shuffled {
        println!("Deck has been shuffled!");
    }
}

7. Ownership, Borrowing & Lifetimes

  • Every value is 'owned' by a single variable, argument, struct, vector, etc at a time

  • Reassigning the value to avariable, passing it to afunction, puting it into a vector, etc, moves the value. The old owner can't be used to access the value anymore!

  • You can create many read-only references to a value that exist at the same time. These refs can all exist at the same time

  • You can't move a value while a ref to the value exists

  • You can make a writeable (mutable) reference to a value only if there are no read-only references currently in use. One mutable ref to a value can exist at a time

  • You can't mutate a value through the owner when any ref (mutable or immutable) to the value exists

  • Some types of values are copied instead of moved (numbers, bools, chars, arrays/tuples with copyable elements)

  • When an owner goes out of scope, the value owned by it is dropped (cleaned up in memory)

  • There can't be references to a value when its owner goes out of scope

  • References to a value can't outlive the value they refer to

8. Function Argument Types

Need to store the argument somewhere?Favor taking ownership (receive a value)
Meed to do a calculation with the value?Favor receiving a read-only ref
Need to change a value in some way?Favor receiving a mutable ref

9. Result & Option

ResultOption
Result is used when we need to know if something worked or failedOption is used when we need to know if a value is present or not
Ok() variant is used when something went wellSome() variant used when we have a value
Err() variant used when something bad happenedNone variant used when there is no value
// If 'item' is a Some, returns the value in the Some

// If 'item' is a None, panics!
// Use for quick debugging or examples
item.unwrap();
// If 'item' is a None, prints the provided debug message and panics!
// Use when we want to crash if there is no value
item.expect("There should be a value here");
// If 'item' is a None, returns the provided default value
// Use when it makes sense to provide a fallback value

//If 'text' is an Ok, returns the value in the Ok

// If 'text' is an Err, panics!
// Use for quick debugging or examples
text.unwrap();
// If 'text' is an Err, prints the provided debug message and panics!
// Use when we want to crash if something goes wrong
text.expect("couldnt open the file");
// If'text' is an Err, returns the provided default value
// Use when you want a fallback default value in case something goes wrong
text.unwrap_or( String::from("backup text"));

10. String & Slice

NameWhen to useUses memory inNotes
StringWhen you want to take ownership of text data.
When you have a string that might grow or shrink.
Stack & Heap
&StringUsually neverStackRust automatically turns &String into a &str for you.
&strWhen you want to read al or a portion of some text owned by something else.StackRefers directly to heap-allocated or data-allocated text

11. '?' Operator

  • '?' operator gets added onto functions that return a Result
  • If the function returns an Ok, the value inside is automatically extracted
  • If it contains an Err, the Err() variant is automatically returned
NameWhen to use
'match' or 'if let'When you're ready to meaningfully deal with an error
'unwrap()' or 'expect("why this paniced")'Quick debugging, or if you want ot crash on an Err()
operator '?'When you dont' have any way to handle the error in the current function

12. Iterators

  • Used to iterate over any kind of data structure
  • We've already been using them - they are used behind the scenes when you write a for loop
  • Follow al the same rules of ownership, borrowing, lifetimes
  • Use the Option enum
  • We usually don't call 'next' on an iterator manually
    • Use a for loop. Automatically creates an iterator and calls 'next' on it
    • Use iterator adaptors and consumers like 'for_each', 'collect', 'map', etc
  • Iterators are lazy'. Nothing happens until...
    • You cal 'next'
    • You use a function that cals 'next' automatically
NameWhen to use
iter()The iterator will give you a read-only reference to each element
into_iter()The iterator give you ownership of each element
iter_mut()The iterator will give you a mutable reference to each element
  • into_iter() wil give you something different depending on how its called
NameNotes
&colors.into_iter()Iterator created out of a reference
Iterator will produce refs to each value
&mutcolors.into_iter()Iterator created out of a mutable reference
Iterator will produce mutable refs to each value
colors.into_iter()Iterator created out foa value
Iterator will produce each value. Aslo moves ownership of these values.

13. Lifetime annotations

  • Help the compiler make sure refs wont outlive the value they refer to
struct Account {
    balance: i32,
}
struct Bank<'a> {
    primary_account: &'a Account,
}
fn longest<'a>(str_a: &'a str, str_b: &'a str) => &'a str {
    if str_a.len() >= str_b.len() {
        str_a
    }else{
        str_b
    }
}

14. Traits

  • Traits define common functionality between different types
  • The implementor has to provide an implementation for all of the abstract methods
  • The implementor can optionally override the default methods
pub trait Vehicle {
    // default method
    fn change_ties(&mut self){
        println!("Changing tires!");
    }
    // abstract method
    fn tire_change_required(&self)->bool;
    fn record_tire_change(&mut self);
}
pub trait BatteryPowered: Vehicle {
    fn service_electric_motor(&self);
}
pub trait GasPowered: Vehicle {
    fn service_fuel_tank(&self);
}

struct Tesla {}
impl Vehicle for Tesla {
    fn tire_change_required(&self)->bool{}
    fn record_tire_change(&mut self){}
}

struct Civic {}
impl Vehicle for Civic {
    fn tire_change_required(&self)->bool{}
    fn record_tire_change(&mut self){}
}

fn service_car(car: impl Vehicle){
    car.change_ties();
}
fn main(){
    let tesla = Tesla {};
    let civic = Civic {};
    service_car(tesla);
    service_car(civic);
}

15. Generic

pub struct Basket<T>{
    pub item: Option<T>,
}
impl<T> Basket<T> {
    fn get(&mut self) -> Option<T> {
        self.item.take()
    }
    fn put(&mut self, item: T){
        self.item = Some(item);
    }
}
fn main(){
    let item = String::from("dp");
    let basket = Basket {item: Some(item)}
    let basket_1 = Basket {item: Some(1)}
}

16. Pattern Matching

  • Basic
let x = 5;
match x {
    // matching literals
    1 => println!("one"),
    // matching multiple patterns
    2 | 3 => println!("two or three"),
    // matching ranges
    4..=9 => println!("within range"),
    // matching named variables
    x => println!("{}", x),
    // default case (ignores value)
    _ => println!("default Case")
}
  • Destructuring
struct Point {
    x: i32,
    y: i32,
}
let p = Point { x: 0, y: 7 };
match p {
    Point { x, y: 0 } => {
        println!("{}", x);
    },
    Point { x, y } => {
        println!("{} {}", x, y);
    },
}

enum Shape {
    Rectangle { width: i32, height: i32 },
    Circle(i32),
}
let shape = Shape::Circle(10);
match shape {
    Shape::Rectangle { x, y } => //...
    Shape::Circle(radius) => //...
}
  • Ignoring values
struct SemVer(i32, i32, i32);
let version = SemVer(1, 32, 2);
match version {
    SemVer(major, _, _) => {
        println!("{}", major);
    }
}
let numbers = (2, 4, 8, 16, 32);
match numbers {
    (first, .., last) => {
        println!("{}, {}", first, last);
    }
}
  • Match guards
let num = Some(4);
match num {
    Some(x) if x < 5 => println!("less than five: {}", x),
    Some(x) => println!("{}", x),
    None => (),
}
  • @ bindings
struct User {
id: i32
}
let user = User { id: 5 };
match user {
    User {
        id: id_variable @ 3..=7,
    } => println!("id: {}", id_variable),
    User { id: 10..=12 } => {
        println!("within range");
    },
    User { id } => println!("id: {}", id),
}

17. Smart Pointers

  • Box - for allocating values on the heap
  • Rc - multiple ownership with reference counting
  • Ref, RefMut, and RefCell - enforce borrowing rules at runtime instead of compile time.
let b = Box::new(1);

let a = Rc::new(1);
let b = Rc::clone(&a);

let r1 = RefCell::new(1);
let r2 = r1.borrow(); // Ref - immutable borrow
let r3 = r1.borrow_mut(); // RefMut - mutable borrow

// Create an Rc-wrapped RefCell containing an integer
let shared_value = Rc::new(RefCell::new(0));

// Clone Rc to create a second owner
let value1 = Rc::clone(&shared_value);
let value2 = Rc::clone(&shared_value);

// Modify the value through one of the owners
*value1.borrow_mut() += 1;

// Access the updated value through the other owner
println!("Value after modification: {}", *value2.borrow()); // Result: 1

References