| Feature | Description | Impact |
|---|---|---|
| Move | Ownership transfer | Old invalid |
| Copy | Bitwise copy | Both valid |
| Drop | Cleanup | Memory freed |
| Feature | Copy | Clone |
|---|---|---|
| Method | Implicit | Explicit |
| Memory | Stack | Heap |
| Speed | Fast | Slower |
| Scenario | Result |
|---|---|
| Ref < Owner | Safe |
| Ref > Owner | Error |
| Type | Meaning |
|---|---|
| 'a | Named lifetime |
| 'static | Entire program |
| Rule | Description |
|---|---|
| Rule 1 | Each ref gets lifetime |
| Rule 2 | Input lifetime ? output |
| Rule 3 | &self used |
| Context | Meaning |
|---|---|
| Reference | Lives entire program |
| Trait Bound | No non-static refs |
| Global | Permanent memory |
| Type | Thread Safe |
|---|---|
| Cell | No |
| RefCell | No |
| Mutex | Yes |
| RwLock | Yes |
| Strategy | Benefit |
|---|---|
| Clone | Simpler lifetimes |
| Short scope | Less conflict |
| Owned types | No lifetimes |
| Feature | String | &str |
|---|---|---|
| Ownership | Owned | Borrowed |
| Memory | Heap | Any |
| Mutable | Yes | No |
| Variant | Use |
|---|---|
| Unit | Simple state |
| Tuple | Grouped data |
| Struct | Readable fields |
The match Operator and Exhaustiveness
In Rust, the match operator is a powerful control
flow construct that allows you to compare a value against a
series of patterns. The compiler enforces
exhaustiveness, meaning every possible value of
the type being matched must be covered by at least one "arm."
Core Mechanics of Match
- Pattern Matching: Each arm consists of a pattern and the code to run if the value fits that pattern.
-
Exhaustivity Check: If you are matching an
enum, the compiler checks every variant. If you miss one, the code will not compile. -
Binding:
matchcan deconstruct types (like enums or structs) to bind internal data to variables for use in the arm's logic.
| Pattern Type | Syntax | Purpose |
|---|---|---|
| Literal | 1 => ... |
Matches a specific constant value. |
| Named Variable | Some(value) => ... |
Matches and binds the inner value to a variable. |
| Multiple Patterns | 1 | 2 => ... |
Matches any of the provided patterns. |
| Ranges | 1..=5 => ... |
Matches any value within the inclusive range. |
| Placeholder ( _ ) | _ => ... |
The "catch-all" pattern that matches anything not previously handled. |
Example: Exhaustive Enum Match
Why Exhaustiveness Matters
Exhaustive matching prevents a common class of bugs where a
developer adds a new variant to an enum but forgets to update
the logic throughout the codebase. In Rust, the compiler will
immediately point out every match statement that
needs attention, ensuring the software remains robust as it
evolves.
Would you like to explore "Match Guards" (the
if condition within a match arm) in more detail?
Traits in Rust
A Trait is a collection of methods defined for
an unknown type: Self. They define shared behavior
that types can implement. When a type implements a trait, it
promises to provide the functionality described by that trait's
method signatures.
Comparison: Traits vs. Interfaces
While they serve a similar purpose—polymorphism and defining contracts—there are fundamental differences in how they are applied and structured.
| Feature | Rust Traits | Java/C# Interfaces |
|---|---|---|
| Implementation | Can be implemented for any type anywhere (External). | Must be declared at the time the class is defined (Internal). |
| Default Methods | Supports default implementations. | Supports default implementations (in newer versions). |
| State | Cannot contain fields/state. | Cannot contain fields/state. |
| Blanket Impls | Can implement a trait for all types that satisfy another trait. | Not supported. |
| Coherence | Orphan rules prevent conflicting implementations. | Interface collisions are handled via explicit implementation/namespacing. |
Key Characteristics
-
Ad-hoc Polymorphism: You can implement a
trait for a type you didn't create. For example, you can
implement your own
Summarytrait for the standard library'sVectype. -
Trait Bounds: You can restrict generic
functions so they only accept types that implement a specific
trait (e.g.,
fn calculate<T: Math>(item: T)). -
Derivability: Many common traits (like
Debug,Clone, orDefault) can be automatically implemented by the compiler using the#[derive(...)]attribute.
Example Syntax
Trait Bounds in Generics
Trait Bounds are a way to restrict generic type
parameters to only those types that implement specific behaviors
(traits). Without trait bounds, a generic type T is
treated as a completely unknown type, and the compiler will not
allow you to perform any operations on it (like addition or
printing).
How They Work
When you define a generic function or struct, you specify a
"bound" that tells the compiler: "This function works with any
type T, as long as T has implemented
these specific methods."
| Syntax Type | Example | Use Case |
|---|---|---|
| Inline Bound | fn func<T: Display>(item: T) |
Best for simple, single-trait constraints. |
| Where Clause |
fn func<T>(item: T) where T: Display +
Clone
|
Best for multiple parameters or complex bounds to keep signatures readable. |
| Multiple Bounds | T: Display + PartialOrd |
When a type must satisfy several traits at once. |
The "Why" Behind Trait Bounds
- Monomorphization: Rust generates specific machine code for each concrete type used with the generic function. Trait bounds ensure that this generated code is valid.
- Early Error Detection: The compiler checks that the trait requirements are met at the call site, rather than than inside the function body, leading to clearer error messages.
-
Functionality Access: They "unlock" methods.
For example, a bound of
T: Addallows you to use the+operator on variables of typeT.
Example: Restricting a Generic Function
Blanket Implementations
Trait bounds also allow "Blanket Impls," where you can implement
a trait for any type that already satisfies another
trait. For example, the standard library implements
ToString for any type that implements
Display.
Static vs. Dynamic Dispatch
In Rust, dispatch refers to how the computer decides which implementation of a trait method to run when a call is made. Rust provides two mechanisms to handle this, balancing performance and flexibility.
Comparison Table
| Feature | Static Dispatch ( <T: Trait> ) | Dynamic Dispatch ( &dyn Trait ) |
|---|---|---|
| Mechanism | Monomorphization (compiler generates code for each type). | Vtable (Virtual Method Table) lookup at runtime. |
| Performance | Faster; allows inlining and compiler optimizations. | Slower; involves pointer indirection and prevents inlining. |
| Binary Size | Can increase due to "code bloat" (multiple versions of a function). | Smaller; only one version of the function exists. |
| Flexibility | Limited to a single concrete type per call site. |
Allows collections of different types (e.g.,
Vec<Box<dyn Trait>>).
|
| Syntax |
Generic bounds:
fn func<T: Trait>(arg: T).
|
Trait objects:
fn func(arg: &dyn Trait).
|
Static Dispatch (Monomorphization)
When you use generics, Rust generates a copy of the function for every concrete type you use. This is called Static Dispatch because the specific function to call is determined at compile time.
Dynamic Dispatch (Trait Objects)
Sometimes you need to store different types together (e.g., a
list containing both "Circles" and "Squares" that both implement
"Draw"). Since the concrete types differ, their sizes differ, so
you must use a reference or a box (&dyn Trait
or Box<dyn Trait>). Rust uses a
vtable to find the correct method address at
runtime.
The "Object Safety" Constraint
Not all traits can be used for dynamic dispatch. For a trait to
be "object safe" (and thus usable with dyn), it
generally cannot:
- Have methods that return
Self. - Have methods with generic type parameters.
Would you like to see an example of how to use
Box<dyn Trait> to create a list of objects
with different underlying types?
The Option<T> Enum
In Rust, there is no null value. Instead, the
language uses the Option<T> enum to represent
the presence or absence of a value. This forces developers to
explicitly handle the "empty" case at compile time, eliminating
the common "NullPointerException" found in other languages.
Structure of Option<T>
Option<T> is a standard library enum defined
as follows:
How it Replaces Null
| Feature | Traditional Null | Rust Option<T> |
|---|---|---|
| Safety | Implicit; any object could be null, leading to runtime crashes. |
Explicit; you cannot use T as if it were an
Option<T>.
|
| Compiler Support | Often ignored by compilers; requires manual checks. |
The compiler forces you to handle the
None case before accessing the data.
|
| Type System |
Null is often a member of every type.
|
Option<T> is a distinct type from
T.
|
| Clarity | Method signatures don't show if a return can be null. |
-> Option<i32> clearly signals the
caller that a value might be missing.
|
Handling Option<T>
Because Option<T> is an enum, you typically
use pattern matching or specialized helper methods to access the
inner value:
-
Pattern Matching:
25th
-
Unwrapping:
-
.unwrap(): Returns the value or panics ifNone(use only when certain). -
.expect("Error msg"): Like unwrap, but with a custom crash message. -
.unwrap_or(default): Returns a default value ifNone.
-
-
The ? Operator: Used to return
Noneearly from a function if the option isNone.
The "Why" Behind the Design
By making the absence of a value a first-class type, Rust turns a potential runtime logic error into a compile-time requirement. You are effectively "wrapping" your data in a box; to get the data out, you must first check if the box is empty.