Rust
Installation
Preferably - use the rustup version manager through mise
# through mise
$ mise use rust@stable
$ rustc --version
rustc 1.91.1 (ed61e7d7e 2025-11-07)
$ which rustc
/Users/ata/.local/share/mise/shims/rustc
$ which rustup
/Users/ata/.local/share/mise/shims/rustup
# through rustup directly
brew install rustup
rustup --version
rustup update
rustup show
rustup toolchain install stable
rustup toolchain uninstall
rustup doc
or bare version install
brew install rust
rustc --version
cargo --version
cargo new project_name # creates a new project
cargo run # compiles and runs --release|debug --target x86_64...
Ownership
Core memory management concept. Replaces Garbage Collection and manual malloc/free.
###Ownership Rules 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.
###Move Semantics Default behavior: Assigning non-primitive types (like String, Vec) transfers ownership ("moves"). C++ Difference: C++ copies by default; Rust moves by default to prevent double-free errors. Copy: Primitives (i32, bool, etc.) implement Copy trait and are duplicated.
let s1 = String::from("hello");
let s2 = s1; // Ownership moved to s2
// println!("{}", s1); // Compile Error: s1 is invalid
Borrowing (References)
Access data without taking ownership. Immutable Reference (&T): Read-only. Unlimited active copies allowed. Mutable Reference (&mut T): Read/Write. Only one active copy allowed at a time. The Golden Rule: You can have either one mutable reference or any number of immutable references, but not both simultaneously.
let mut s = String::from("hello");
let r1 = &s; // immutable borrow
let r2 = &s; // multiple immutable OK
// let r3 = &mut s; // Error: cannot borrow mutably while borrowed immutably
println!("{} {}", r1, r2); // usage ends here
let r3 = &mut s; // OK, previous borrows not used after this point
Crates
Crates are Rust's compilation units, packages, libraries and binaries crates.io is the official crate registry.
Prelude
The prelude is a collection of commonly used items that Rust automatically imports into every program. It's part of the standard library (std::prelude) and saves you from having to manually import frequently used types and traits.
Struct Methods vs Struct Functions
- Functions: Don't take
selfas a parameter - Functions: Called using
::syntax (e.g.,String::new()) - Functions:Often used as constructors or utility functions
- Functions: Cannot access instance data
- Methods: Take
selfas a parameter - Methods: Called using
.syntax (e.g.,my_string.len()) - Methods: Can access instance data
- Methods: Can be defined on any type, including primitive types like
i32 - Methods: Can be defined on any type, including primitive types like
i32
Tuple Structs
Tuple structs are a hybrid between tuples and structs. They have a name like regular structs, but their fields are unnamed and accessed by position (like tuples).
Boxing in Rust
Box<T> is a smart pointer that allocates data on the heap rather than the stack. It's useful when:
- You have a type whose size can't be known at compile time
- You want to transfer ownership of a large amount of data without copying
- You want to own a value and only care that it implements a particular trait (trait objects)
Box<T>provides ownership for heap-allocated data- When a
Box<T>goes out of scope, both the box (stack) and the data it points to (heap) are deallocated - Dereferencing a box is automatic in most contexts
Example
let b = Box::new(5);
println!("b = {}", *b); // dereference the box to get the value
Traits
Rust's version of Interfaces or C++ Abstract Base Classes. Defines shared behavior (method signatures). Implementation: Separate from the struct data (impl Trait for Type). Derive: #[derive(Debug, Clone)] auto-generates impls for common traits.
trait Summary {
fn summarize(&self) -> String; // Pure virtual equivalent
}
impl Summary for User {
fn summarize(&self) -> String { format!("{}", self.name) }
}
// Static: "T must implement Summary"
fn notify<T: Summary>(item: &T) { ... }
Lifetimes
Ensures references are valid for as long as they are used to prevent dangling pointers.
Concept: Connects the scope of input arguments to the scope of return values.
Elision: Compiler infers most lifetimes automatically.
Explicit Syntax: Required when a function returns a reference or a struct holds a reference.
Syntax: Generic parameter starting with ' (e.g., <'a>).
// Function: Return value must live at least as long as inputs x AND y
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// Struct: The struct instance cannot outlive the reference it holds
struct Highlight<'a> {
part: &'a str,
}
// 'static: Lives for the entire program duration (e.g., string literals)
let s: &'static str = "I have no owner";
Error Handling
No Exceptions. Uses return types (Enums). Forces explicit handling.
Unrecoverable: panic!
Aborts thread/program.
C++: Like std::terminate() or uncaught exception.
Recoverable: Result<T, E>
enum Result<T, E> { Ok(T), Err(E) }
C++: Like std::expected (C++23).
? Operator: Propagates error. If Err, returns early. Replaces try/catch or if (err) return err;.
Nulls: Option<T>
enum Option<T> { Some(T), None }
C++: Replaces nullptr. Compiler prevents accessing None as a value.
use std::fs::File;
use std::io::Read;
fn read_file() -> Result<String, std::io::Error> {
// With ? operator:
let mut f = File::open("file.txt")?;
// Equivalent without ? operator:
// let mut f = match File::open("file.txt") {
// Ok(file) => file,
// Err(e) => return Err(e),
// };
let mut contents = String::new();
f.read_to_string(&mut contents)?; // ? propagates any read errors
Ok(contents)
}
// Using the function with ? operator:
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Option 1: Propagate error with ?
// When main returns Result and uses ?, the error goes to the runtime
// which prints it and exits with non-zero code (similar to C's return 1)
let contents = read_file()?;
println!("File contents: {}", contents);
// Option 2: Handle error with match (stays in the program)
match read_file() {
Ok(contents) => println!("Success: {}", contents),
Err(e) => eprintln!("Error: {}", e),
}
Ok(()) // Success: exits with code 0
}
// panic!("Critical failure"); // Crashing explicitly