if expressions: Must always have a Boolean condition (be explicit)┌─────────────────────────────────────────────────────────────┐
│ RUST MEMORY MODEL │
├─────────────────────────────────────────────────────────────┤
│ │
│ STACK HEAP │
│ ├─ Known, fixed size ├─ Unknown/dynamic size │
│ ├─ Fast access ├─ Slower access │
│ ├─ Automatic management ├─ Manual management │
│ └─ Not allocating └─ Requires allocation │
│ │
└─────────────────────────────────────────────────────────────┘drop Function})fn main() {
let s = String::from("hello"); // s owns the String
takes_ownership(s); // s's value MOVES
// s is no longer valid!
let x = 5; // x owns the i32
makes_copy(x); // i32 is Copy, so x is still valid
// x can still be used
} // x goes out of scope, then s
// Since s was moved, nothing happens for s
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string dropped, memory freed
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
} // Nothing special happens (Copy type)| Feature | Copy | Clone |
|---|---|---|
| How it works | Implicit bitwise copy | Explicit method call .clone() |
| Syntax | Automatic | Must call .clone() |
| Performance | Always cheap (stack) | Can be expensive (heap) |
| Memory | Stack only | Stack or Heap |
| Requirements | Must also implement Clone | Can exist alone |
Copyi32, u64, etc.f32, f64bool, charCopy)&T (but NOT &mut T)CopyString, Vec, Box are NOT Copy)CloneCopy&mut T), OR&T)A data race occurs when:
Rust prevents data races at compile time!
If you have a reference to data, the compiler ensures that the data will not go out of scope before the reference does.
// Compiler prevents this:
let r;
{
let x = 5;
r = &x; // ❌ ERROR: x doesn't live long enough
}
println!("{}", r); // x is already dropped!Generics allow you to write code that works with multiple types while maintaining type safety and performance. Type parameters are declared using angle brackets <>.
Key Benefit: Zero runtime cost - compiler creates specialized code for each type (monomorphization).
// Basic generic function
fn print_value<T>(value: T)
where
T: std::fmt::Display
{
println!("Value: {}", value);
}
// Usage
print_value(42); // Works with i32
print_value("hello"); // Works with &str
print_value(3.14); // Works with f64// Single type parameter
struct Container<T> {
value: T,
}
impl<T> Container<T> {
fn new(value: T) -> Self {
Container { value }
}
fn get(&self) -> &T {
&self.value
}
}
// Usage
let int_container = Container::new(42);
let string_container = Container::new(String::from("hello"));struct Pair<T, U> {
first: T,
second: U,
}
impl<T, U> Pair<T, U> {
fn new(first: T, second: U) -> Self {
Pair { first, second }
}
fn get_first(&self) -> &T {
&self.first
}
fn get_second(&self) -> &U {
&self.second
}
}
// Usage - different types for each field
let pair = Pair::new(42, "hello");
let another = Pair::new(String::from("world"), 3.14);// Option-like enum
enum MyOption<T> {
Some(T),
None,
}
// Result-like enum
enum MyResult<T, E> {
Ok(T),
Err(E),
}
// Usage
let some_number: MyOption<i32> = MyOption::Some(5);
let no_number: MyOption<i32> = MyOption::None;struct Point<T> {
x: T,
y: T,
}
// Generic implementation for all types
impl<T> Point<T> {
fn new(x: T, y: T) -> Self {
Point { x, y }
}
}
// Specific implementation only for f64
impl Point<f64> {
fn distance_from_origin(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
// Usage
let int_point = Point::new(5, 10);
let float_point = Point::new(3.0, 4.0);
println!("Distance: {}", float_point.distance_from_origin()); // Only works with f64struct Stack<T> {
items: Vec<T>,
}
impl<T> Stack<T> {
fn new() -> Self {
Stack { items: Vec::new() }
}
fn push(&mut self, item: T) {
self.items.push(item);
}
fn pop(&mut self) -> Option<T> {
self.items.pop()
}
fn is_empty(&self) -> bool {
self.items.is_empty()
}
fn size(&self) -> usize {
self.items.len()
}
}
// Usage with different types
let mut int_stack = Stack::new();
int_stack.push(1);
int_stack.push(2);
println!("{:?}", int_stack.pop()); // Some(2)
let mut string_stack = Stack::new();
string_stack.push(String::from("hello"));
string_stack.push(String::from("world"));
println!("{:?}", string_stack.pop()); // Some("world")Traits define shared behavior - like interfaces in other languages. They specify a set of methods that types must implement.
// Define a trait
trait Describe {
fn describe(&self) -> String;
}
// Implement for different types
struct Dog {
name: String,
}
impl Describe for Dog {
fn describe(&self) -> String {
format!("A dog named {}", self.name)
}
}
struct Cat {
name: String,
}
impl Describe for Cat {
fn describe(&self) -> String {
format!("A cat named {}", self.name)
}
}
// Usage
let dog = Dog { name: String::from("Buddy") };
let cat = Cat { name: String::from("Whiskers") };
println!("{}", dog.describe());
println!("{}", cat.describe());trait Summary {
fn summarize_author(&self) -> String;
// Default implementation
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
struct Article {
author: String,
content: String,
}
impl Summary for Article {
fn summarize_author(&self) -> String {
self.author.clone()
}
// Uses default summarize() implementation
}
let article = Article {
author: String::from("John"),
content: String::from("Rust is great!"),
};
println!("{}", article.summarize());// Function that works with any type implementing Display
fn print_it<T: std::fmt::Display>(value: T) {
println!("{}", value);
}
// Multiple trait bounds using +
fn process<T: Clone + std::fmt::Display>(value: T) {
let copy = value.clone();
println!("{}", copy);
}// Instead of this:
fn complex<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {
// ...
}
// Use where clause for better readability:
fn complex<T, U>(t: T, u: U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
// ...
}fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
// Usage
let numbers = vec![34, 50, 25, 100, 65];
println!("Largest: {}", largest(&numbers));
let chars = vec!['y', 'm', 'a', 'q'];
println!("Largest: {}", largest(&chars));Pros: Fast, compiler optimizes, can inline
Cons: Code size increases (monomorphization), can't mix types
trait Animal {
fn make_sound(&self) -> String;
}
struct Dog;
struct Cat;
impl Animal for Dog {
fn make_sound(&self) -> String {
"Woof!".to_string()
}
}
impl Animal for Cat {
fn make_sound(&self) -> String {
"Meow!".to_string()
}
}
// Generic function - compiler creates separate code for each type
fn animal_sound<T: Animal>(animal: &T) -> String {
animal.make_sound()
}
// Usage - each type known at compile time
let dog = Dog;
let cat = Cat;
println!("{}", animal_sound(&dog));
println!("{}", animal_sound(&cat));
// ❌ Cannot mix types in a collection
// let animals = vec![Dog, Cat]; // Error!Pros: Can mix types, smaller code size, flexible
Cons: Slight runtime overhead (vtable lookup), can't inline
// Function using trait object
fn animal_sound_dyn(animal: &dyn Animal) -> String {
animal.make_sound()
}
// ✅ CAN mix types in collections!
let animals: Vec<Box<dyn Animal>> = vec![
Box::new(Dog),
Box::new(Cat),
Box::new(Dog),
];
for animal in animals {
println!("{}", animal.make_sound());
}trait Widget {
fn draw(&self);
}
struct Button {
label: String,
}
struct TextBox {
content: String,
}
impl Widget for Button {
fn draw(&self) {
println!("Drawing button: {}", self.label);
}
}
impl Widget for TextBox {
fn draw(&self) {
println!("Drawing textbox: {}", self.content);
}
}
// With trait objects - can store mixed types!
struct Screen {
components: Vec<Box<dyn Widget>>,
}
impl Screen {
fn new() -> Self {
Screen { components: Vec::new() }
}
fn add_component(&mut self, component: Box<dyn Widget>) {
self.components.push(component);
}
fn render(&self) {
for component in &self.components {
component.draw();
}
}
}
// Usage
let mut screen = Screen::new();
screen.add_component(Box::new(Button { label: "Submit".to_string() }));
screen.add_component(Box::new(TextBox { content: "Email".to_string() }));
screen.add_component(Box::new(Button { label: "Cancel".to_string() }));
screen.render();| Feature | Generic <T: Trait> | Trait Object dyn Trait |
|---|---|---|
| Dispatch | Static (compile-time) | Dynamic (runtime) |
| Performance | Faster (no vtable) | Slightly slower |
| Code size | Larger (copies for each type) | Smaller |
| Mixed types | ❌ No | ✅ Yes |
| Inline-able | ✅ Yes | ❌ No |
| Trait bounds | Multiple traits OK | Limited (object-safe only) |
Use Generics when:
Use Trait Objects when:
A trait is "object-safe" if it can be used as a trait object (dyn Trait). Not all traits can be trait objects!
trait Container {
fn add<T>(&mut self, item: T); // Generic method - NOT object safe!
}
// This won't compile:
// let container: Box<dyn Container> = Box::new(MyContainer);
// Error: trait cannot be made into an object
// ✅ Solution: Make the trait generic instead
trait Container<T> {
fn add(&mut self, item: T); // No longer generic method
}
let container: Box<dyn Container<i32>> = Box::new(IntContainer); // Works!Why? The compiler can't create a vtable with infinite generic variations.
Selftrait Cloneable {
fn clone_me(&self) -> Self; // Returns Self - NOT object safe!
}
// This won't compile:
// let obj: Box<dyn Cloneable> = Box::new(MyStruct);
// Error: method returns `Self`
// ✅ Solution: Return a trait object
trait Cloneable {
fn clone_box(&self) -> Box<dyn Cloneable>;
}
impl Cloneable for MyStruct {
fn clone_box(&self) -> Box<dyn Cloneable> {
Box::new(MyStruct { value: self.value })
}
}
let obj: Box<dyn Cloneable> = Box::new(MyStruct { value: 42 });
let cloned = obj.clone_box(); // Works!Why? With type erasure, the compiler doesn't know what concrete type to return.
self)trait Factory {
fn create() -> Self; // No self parameter - NOT object safe!
}
// This won't compile:
// let factory: Box<dyn Factory> = Box::new(Product);
// Error: associated function has no `self` parameter
// ✅ Solution: Add a self parameter
trait Factory {
fn create(&self) -> Box<dyn Factory>; // Now has &self
}
let factory: Box<dyn Factory> = Box::new(ProductFactory);
let new_factory = factory.create(); // Works!Why? Associated functions are called on the type itself, but trait objects erase the type.
where Self: Sizedtrait MyTrait {
fn method(&self) where Self: Sized; // NOT object safe!
}Why? Trait objects are !Sized (unsized), which contradicts the Sized requirement.
A trait is object-safe if ALL methods:
self parameter (&self, &mut self, or self)Self (unless wrapped in Box<dyn Trait>)where Self: Sized// The standard Clone trait is NOT object safe
pub trait Clone {
fn clone(&self) -> Self; // Returns Self!
}
// ❌ This doesn't work:
// let obj: Box<dyn Clone> = Box::new(String::from("hello"));
// ✅ Object-safe alternative:
trait CloneBox {
fn clone_box(&self) -> Box<dyn CloneBox>;
}
impl<T: Clone + 'static> CloneBox for T {
fn clone_box(&self) -> Box<dyn CloneBox> {
Box::new(self.clone())
}
}
struct Dog {
name: String,
}
impl Clone for Dog {
fn clone(&self) -> Self {
Dog { name: self.name.clone() }
}
}
let dog: Box<dyn CloneBox> = Box::new(Dog { name: "Buddy".to_string() });
let cloned = dog.clone_box(); // Works!Stack-allocated (Copy) Heap-allocated (Clone required)
├─ Primitives (i32, f64) ├─ String
├─ Arrays [T; N] ├─ Vec<T>
├─ Tuples (T, U) ├─ Box<T>
└─ References &T ├─ HashMap<K, V>
└─ Custom structs with heap dataVec<T> when:HashMap<K, V> when:BTreeMap<K, V> when:Note: In theory, BST is optimal (log₂n lookups), but in practice, it's inefficient for modern computer architectures due to cache locality issues.
struct User {
username: String,
email: String,
}
let mut user1 = User { // Entire struct is mutable
username: String::from("user"),
email: String::from("user@example.com"),
};
user1.email = String::from("new@example.com"); // ✅ OKlet user2 = User {
email: String::from("another@example.com"),
..user1 // Copy remaining fields from user1
};self, &self, or &mut self as first parameterinstance.method()self as first parameterType::function()new)impl Rectangle {
// Associated function (constructor)
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
// Method
fn area(&self) -> u32 {
self.width * self.height
}
}
// Usage
let sq = Rectangle::square(10); // Associated function
let area = sq.area(); // Methodselfself (not &self) takes ownershipself into something elsebreak or continue to specific loops')'outer: loop {
loop {
break 'outer; // Breaks the outer loop
}
}if let - Concise Control Flowif and let for pattern matchingmatch when you only care about one caselet some_value = Some(3);
// Instead of:
match some_value {
Some(3) => println!("three"),
_ => (),
}
// Use:
if let Some(3) = some_value {
println!("three");
}Vec<T>)let mut v = Vec::new();
let v = vec![1, 2, 3]; // vec! macro
let mut v = vec![100, 32, 57];let mut v = vec![100, 32, 57];
for i in &mut v {
*i += 50; // Dereference and modify
}let mut v = vec![1, 2, 3];
for i in &v {
v.push(4); // ❌ ERROR: Can't modify while iterating
}The reference held by the for loop prevents simultaneous modification.
HashMap but key-only| Type | Description | Location | Mutable |
|---|---|---|---|
String | Growable, heap-allocated | Heap | Yes |
&str | String slice (view into UTF-8 bytes) | Can be anywhere | No |
let s = "Hello, world!"; // Type: &str&str (immutable references)let s = String::new();
let s = String::from("hello");
let s = "hello".to_string();let s = String::from("hello");
let h = s[0]; // ❌ ERROR: Cannot index into StringReason: Indexing operations should be O(1), but Rust can't guarantee this with UTF-8 strings. Rust would need to walk through from the beginning to validate character boundaries.
main functionrand crateNote: When Rustaceans say "crate", they usually mean library crate.
main functionDeclare modules in the crate root file:
mod garden; // Compiler looks for module code in:mod garden { ... }src/garden.rssrc/garden/mod.rsResult<T, E> Typeenum Result<T, E> {
Ok(T),
Err(E),
}unwrap() Methodmatch expressionResult is Ok(value): returns valueResult is Err: calls panic! macrolet f = File::open("hello.txt").unwrap();
// If file doesn't exist, program panicsNote: Can be verbose and doesn't always communicate intent well. Consider using more specific helper methods or proper error handling.
Send and Sync traits: Extend concurrency guaranteesImportant: When the main thread completes, all spawned threads are shut down, whether or not they finished running.
Send TraitSend: String, Vec<T>, most primitivesSync TraitSync: Arc<T>, Mutex<T>| Trait | Concept | Typical Example | Opposite Example |
|---|---|---|---|
Send | Ownership can move across threads | String, Vec<T> | Rc<T> |
Sync | Shared reference &T can be used across threads | Arc<T>, Mutex<T> | RefCell<T> |
use std::thread;
let data = vec![1, 2, 3];
// `move` keyword transfers ownership to thread
thread::spawn(move || {
println!("{:?}", data);
});Box<T> - Heap AllocationRc<T> - Reference Counting (Single-threaded)Purpose: Shared ownership in single-threaded scenarios
use std::rc::Rc;
let a = Rc::new(5);
let b = Rc::clone(&a); // Increments reference count
let c = Rc::clone(&a); // Now 3 ownersWhy needed? Allows multiple parts of program to own the same data.
Example use case: Doubly-linked list where nodes need to be referenced by multiple other nodes.
Arc<T> - Atomic Reference Counting (Multi-threaded)Purpose: Thread-safe shared ownership
use std::sync::Arc;
let data = Arc::new(vec![1, 2, 3]);
let data_clone = Arc::clone(&data);
std::thread::spawn(move || {
println!("{:?}", data_clone);
});When to use: When you need Rc but across multiple threads.
RefCell<T> - Interior Mutability (Single-threaded)Purpose: Mutable borrows checked at runtime instead of compile time
use std::cell::RefCell;
let x = RefCell::new(5);
*x.borrow_mut() += 1; // Mutable borrowWhy needed? Allows mutation through shared references when compiler can't prove safety at compile time.
Mutex<T> - Mutual Exclusion (Multi-threaded)Purpose: Thread-safe interior mutability
use std::sync::Mutex;
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6;
} // Lock is released hereKey Properties:
Purpose: Indivisible operations that are thread-safe without locks
Available types: AtomicBool, AtomicIsize, AtomicUsize, AtomicPtr, etc.
Guarantee: Operations are not susceptible to race conditions or data corruption.
Create dummy head and tail nodes and connect them together. This simplifies edge case handling.
Consider these for clarity:
remove(node): Remove node from listinsert(node): Insert node at tail (most recent)get(): Remove node, then insert at tail (even if already there)put(): Insert new/updated node, then remove LRU if capacity exceededuse std::rc::Rc;
use std::cell::RefCell;
struct Node {
key: i32,
value: i32,
prev: Option<Rc<RefCell<Node>>>,
next: Option<Rc<RefCell<Node>>>,
}Why Rc<RefCell<Node>>?
| Component | Purpose | What it provides |
|---|---|---|
Rc | Shared ownership | Multiple nodes reference same node |
RefCell | Interior mutability | Modify through shared references |
Rc solves shared ownership:
// Without Rc - doesn't work!
struct Node {
prev: Option<Box<Node>>, // ❌ Box owns exclusively
}
// Node A can't be owned by both Node B's prev AND Node C's nextRefCell solves mutability:
// Without RefCell - doesn't work!
fn remove_node(node: Rc<Node>) {
node.prev = None; // ❌ Can't mutate through Rc
}
// With RefCell - works!
fn remove_node(node: Rc<RefCell<Node>>) {
node.borrow_mut().prev = None; // ✅ Runtime-checked mutation
}use std::sync::{Arc, Mutex};
struct Node {
key: i32,
value: i32,
prev: Option<Arc<Mutex<Node>>>,
next: Option<Arc<Mutex<Node>>>,
}Why Arc<Mutex<Node>>?
| Component | Purpose | What it provides |
|---|---|---|
Arc | Thread-safe shared ownership | Multiple threads reference same node |
Mutex | Thread-safe interior mutability | Only one thread accesses at a time |
pub fn remove(&self, node: Arc<Mutex<Node>>) {
let prev = node.lock().unwrap().prev.take().unwrap();
let next = node.lock().unwrap().next.take().unwrap();
prev.lock().unwrap().next = Some(Arc::clone(&next));
next.lock().unwrap().prev = Some(Arc::clone(&prev));
}Single-threaded Multi-threaded
└─ Rc<RefCell<T>> └─ Arc<Mutex<T>>
├─ Shared ownership ├─ Thread-safe ownership
└─ Runtime borrow checking └─ Thread-safe mutationAnonymous functions that can capture their environment.
// Regular function
fn add_one(x: i32) -> i32 {
x + 1
}
// Closure (types can be inferred)
let add_one = |x: i32| x + 1;
let add_one = |x| x + 1; // Type inferred
// Using it
let result = add_one(5); // 6let multiplier = 10;
// Closure captures 'multiplier' from surrounding scope
let multiply = |x| x * multiplier;
println!("{}", multiply(5)); // 50Key difference from functions: Closures can capture variables from their environment.
let s = String::from("hello");
// 1. Borrows immutably (Fn)
let print = || println!("{}", s);
// 2. Borrows mutably (FnMut)
let mut count = 0;
let mut increment = || count += 1;
// 3. Takes ownership (FnOnce)
let consume = || drop(s); // Moves 's' into closure| Trait | Behavior | Can call multiple times? |
|---|---|---|
Fn | Borrows immutably | ✅ Yes |
FnMut | Borrows mutably | ✅ Yes |
FnOnce | Takes ownership | ❌ No (only once) |
let numbers = vec![1, 2, 3, 4, 5];
// Filter even numbers
let evens: Vec<_> = numbers.iter()
.filter(|&x| x % 2 == 0)
.collect();
// Map to double each
let doubled: Vec<_> = numbers.iter()
.map(|x| x * 2)
.collect();fn do_twice<F>(f: F)
where
F: Fn(i32) -> i32
{
println!("{}", f(5));
println!("{}", f(10));
}
do_twice(|x| x * 2);
// Output: 10, 20use std::thread;
let data = vec![1, 2, 3];
// 'move' keyword transfers ownership
thread::spawn(move || {
println!("{:?}", data);
});use std::fmt;
struct Person {
name: String,
age: u32,
}
// Display - for user-facing output
impl fmt::Display for Person {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} (age {})", self.name, self.age)
}
}
// Debug - for debugging (can also use #[derive(Debug)])
impl fmt::Debug for Person {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Person")
.field("name", &self.name)
.field("age", &self.age)
.finish()
}
}
let person = Person { name: "Alice".to_string(), age: 30 };
println!("{}", person); // Uses Display
println!("{:?}", person); // Uses DebugDisplay Trait (std::fmt::Display)Purpose: Format trait for user-facing output ({} in format strings)
Benefits:
Display automatically implements ToString.to_string() methodDisplay over implementing ToString directlyKey Difference:
Display: User-facing outputDebug: Developer-facing output (can be derived with #[derive(Debug)])// Clone - explicit duplication
#[derive(Clone)]
struct Data {
value: String,
}
let data1 = Data { value: "hello".to_string() };
let data2 = data1.clone(); // Explicit clone
println!("{}", data1.value); // data1 still valid
// Copy - implicit duplication (only for stack-only types)
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
let p1 = Point { x: 1, y: 2 };
let p2 = p1; // Implicit copy
println!("{}", p1.x); // p1 still valid (was copied, not moved)Rules for Copy:
String, Vec, Box)Clone#[derive(PartialEq)]
struct Point {
x: i32,
y: i32,
}
let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: 1, y: 2 };
println!("{}", p1 == p2); // true
// Custom implementation
impl PartialEq for Person {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.age == other.age
}
}#[derive(PartialEq, PartialOrd)]
struct Score(i32);
let s1 = Score(100);
let s2 = Score(200);
println!("{}", s1 < s2); // true// Most common derivable traits
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct MyStruct {
value: i32,
}macro_rules!)macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}#[derive(Debug)] // Procedural macro
struct MyStruct;Rust is designed for safety, but sometimes you need low-level memory operations that the compiler can't verify as safe.
let x = 5;
let raw = &x as *const i32;
unsafe {
println!("{}", *raw);
}unsafe keywordunsafe fn dangerous() {
// Low-level operations
}fn safe_function() {
unsafe {
// Unsafe operations here
}
// Safe operations continue
}unsafe blocksrustc_test FrameworkBuilt-in unit testing system:
#[test] attributeassert_eq!, assert_ne!#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
#[test]
fn it_fails() {
panic!("This test will fail");
}collect()let v: Vec<i32> = (0..10).collect();
let s: String = vec!['h', 'i'].into_iter().collect();
let map: HashMap<_, _> = vec![(1, 2), (3, 4)].into_iter().collect();Use concise names in closures and iterators:
i, j for indicese for eventel for elementx, y for generic valueslet v = vec![1, 2, 3];
// Immutable iteration
for item in &v {
println!("{}", item);
}
// Mutable iteration
for item in &mut v {
*item += 1;
}
// Consuming iteration
for item in v {
// v is moved, can't use v after this
}collect()// Explicit type
let v: Vec<i32> = iter.collect();
// Turbofish syntax
let v = iter.collect::<Vec<i32>>();
// Underscore for partial inference
let v: Vec<_> = iter.collect();// Fast - compiler optimizes
fn process<T: Display>(item: T) {
println!("{}", item);
}// Flexible - can store different types
let items: Vec<Box<dyn Display>> = vec![
Box::new(42),
Box::new("hello"),
];// More readable
fn complex<T, U>(t: T, u: U)
where
T: Display + Clone,
U: Debug + Clone,
{
// ...
}// Makes your types more usable
#[derive(Debug, Clone, PartialEq)]
struct MyType {
// ...
}// Try to create a trait object to check
let obj: Box<dyn MyTrait> = ...;
// Compiler will tell you if it's not object-safe✅ Null pointer dereferences
✅ Data races
✅ Buffer overflows
✅ Use-after-free
✅ Double-free
✅ Iterator invalidation
This guide contains all your original content plus comprehensive generics and traits coverage, organized for easy review and understanding. Use the table of contents to jump to specific sections!