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 Functions | Methods |
---|---|
Use when you have functionality not tied to a specific instance | Use 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 '::' syntax | called 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
Result | Option |
---|---|
Result is used when we need to know if something worked or failed | Option is used when we need to know if a value is present or not |
Ok() variant is used when something went well | Some() variant used when we have a value |
Err() variant used when something bad happened | None 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
Name | When to use | Uses memory in | Notes |
---|---|---|---|
String | When you want to take ownership of text data. When you have a string that might grow or shrink. | Stack & Heap | |
&String | Usually never | Stack | Rust automatically turns &String into a &str for you. |
&str | When you want to read al or a portion of some text owned by something else. | Stack | Refers 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
Name | When 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
Name | When 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
Name | Notes |
---|---|
&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