Rust Primitives
This chapter is for the most basic Rust mental models:
- what a type is
- what a value is
- what actually takes memory
- why
User,&User, and&mut Usermean different things
Types vs Values
A type is a description. A value is the actual thing that exists at runtime.
Example:
#![allow(unused)] fn main() { struct User { name: String, } }
Here:
Useris a type- the type definition itself does not hold runtime data
- it tells Rust what a
Uservalue looks like
Now compare that with:
#![allow(unused)] fn main() { let user = User { name: "Bobby".to_string(), }; }
Here:
useris a value- that value takes memory at runtime
What Actually Takes Memory
The most important rule is:
Values take memory. Types describe values.
So these take memory at runtime:
user: Userx: i32s: Stringv: Vec<i32>r: &Userp: *const i32
These do not hold runtime data by themselves:
Useri32StringVec<i32>
Those are types.
A Better Way To Think About It
Rust has:
- types: descriptions of shape and rules
- values: actual runtime data
- operations: ways to move, borrow, mutate, or inspect those values
So it is not correct to say:
Only types take memory.
It is closer to the opposite:
Values take memory, and Rust types tell the compiler how those values are laid out and how they can be used.
User vs &User vs &mut User
These are different value forms with different ownership meanings.
user: User
#![allow(unused)] fn main() { fn print_user(user: User) { println!("{}", user.name); } }
This means:
- the function takes ownership of the
User - the value is moved into the function
- the caller cannot use it afterward unless it is returned
user: &User
#![allow(unused)] fn main() { fn print_user(user: &User) { println!("{}", user.name); } }
This means:
- the function borrows the
User - the caller keeps ownership
- the function can read from it
- the function does not consume it
user: &mut User
#![allow(unused)] fn main() { fn deactivate(user: &mut User) { user.active = false; } }
This means:
- the function borrows the
Usermutably - the caller still owns it
- the function may modify it
- while that mutable borrow exists, no competing borrows are allowed
Example
struct User { name: String, active: bool, } fn by_value(user: User) { println!("{}", user.name); } fn by_ref(user: &User) { println!("{}", user.name); } fn by_mut_ref(user: &mut User) { user.active = false; } fn main() { let mut user = User { name: "Bobby".to_string(), active: true, }; by_ref(&user); println!("{}", user.name); by_mut_ref(&mut user); println!("{}", user.active); by_value(user); // user is moved here }
Why References Also Take Memory
A reference is also a value. It is a value that points to another value.
Example:
fn main() { let x = 5; let r = &x; }
Here:
xis a value of typei32ris a value of type&i32ralso takes memory because it stores a reference tox
So references are not "just syntax." They are real values with meaning in the program.
Heap vs Stack Intuition
Very roughly:
- simple fixed-size values often live directly in stack frames
StringandVecstore a small control structure directly, and their dynamic contents live on the heap
Example:
#![allow(unused)] fn main() { let n = 5; let s = String::from("rust"); }
nis a small integer valuesis aStringvalue- the
Stringvalue itself stores metadata - the actual text bytes are stored elsewhere on the heap
Tuples and Tuple Patterns
Tuple types in Rust are written structurally.
Example:
#![allow(unused)] fn main() { let pair: (i32, i32) = (19, 34); }
Here:
pairis a value(i32, i32)is the tuple type(19, 34)is the tuple value
There is no built-in type named tuple.
Instead, the tuple type is written directly by listing the element types in parentheses.
Tuple Matching
You can match tuples using tuple patterns:
fn main() { let pair: (i32, i32) = (19, 34); match pair { (0, 0) => println!("origin"), (a, b) => println!("{a}, {b}"), } }
The key idea is:
(0, 0)is a specific tuple pattern(a, b)is a general tuple pattern
That final arm is general for the tuple type being matched. It means:
match any value of type
(i32, i32)and bind its two components toaandb
So in a tuple match, a pattern like (a, b) often acts as the tuple-shaped catch-all arm.
This is why the compiler sees it as exhaustive:
- the matched type is a 2-tuple
(a, b)matches any value of that tuple type
That does not mean it matches "any tuple type" in all contexts. It means it matches any value of the tuple type currently being matched.
Short Mental Model
- Type = description
- Value = runtime thing
- Reference = value that points to another value
- Ownership = who controls the value
- Borrowing = temporary access without transfer of ownership
Short Interview Answer
The core distinction is that types do not hold runtime data; values of those types do. Rust types describe layout and rules, while ownership, borrowing, and references describe how those values are accessed and moved through memory.