Skip to main content

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 self as 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 self as 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