Below is a precise, compiler-level explanation of what is happening in your Rust program, specifically in terms of ownership, referencing, dereferencing, type inference, and what the compiler enforces.
fn main() {
let x = 42;
let y = &x; // y is a reference to x
let z = *y; // dereferencing y to get the value of x
println!("{}", z); // Prints: 42
println!("{}", y);
println!("{}", x);
}What happens conceptually
xis a binding to the integer value42.
Type
x: i32(i32 is inferred by the compiler as the default integer type.)
Memory
xlives on the stack.- The value
42is stored directly inx.
Ownership
xowns the value42.
What happens conceptually
- You create an immutable reference to
x. - No new integer is created.
- No ownership is transferred.
Type
y: &i32Memory
ycontains the address ofx, not the value42.xremains unchanged and still owns the data.
Borrowing rules enforced by the compiler
-
xis immutably borrowed. -
While
yexists:- Any number of immutable borrows are allowed.
- No mutable borrow (
&mut x) is allowed.
This is enforced at compile time, not runtime.
What happens conceptually
*ydereferences the reference.- You are saying: “Go to the memory location that
ypoints to and read the value.”
Type transformation
*y: i32
z: i32Important detail
i32implements theCopytrait.- So the value
42is copied, not moved.
Memory
-
zgets its own independent copy of42. -
After this line:
xowns42zowns another42ystill points tox
Why this works
zis ani32.- It implements
Display.
Ownership
zis passed by copy.- No ownership issues.
This line often confuses people.
What is actually happening
- You are not dereferencing explicitly.
- Rust applies auto-dereferencing for formatting.
Mechanism
-
println!uses theDisplaytrait. -
There is no
Displayfor&i32, but:- Rust sees
&i32 - Automatically dereferences it to
i32 - Prints
42
- Rust sees
This behavior is part of Rust’s deref coercion system.
You could write this explicitly as:
println!("{}", *y);Both are equivalent here.
Why this is allowed
xwas only immutably borrowed.- Immutable borrows do not restrict reads.
- The borrow (
y) is still valid here.
x: i32
y: &i32
z: i32xis never mutated.ynever outlivesx.- No data races are possible.
- All borrowing rules are validated at compile time.
Stack:
x ──► 42
y ──► &x (points to x)
z ──► 42 (copied value)
- References are zero-cost abstractions
- Dereferencing is a simple memory read
- Borrow checking has no runtime overhead
- No garbage collector
- No reference counting
&xcreates a borrow, not a copy.*yreads the value behind the reference.i32isCopy, so dereferencing produces a copy.println!("{}", y)works due to auto-dereferencing.- All safety guarantees are enforced at compile time.
If you want, I can:
- Rewrite this using
&mut - Show what fails to compile and why
- Compare this behavior to C++ pointers
- Explain how this changes with non-
Copytypes likeString
This example is fundamentally different from the previous one, and it does not compile. The reason is entirely about ownership, moves, and dereferencing non-Copy types. I will explain it from the compiler’s perspective.
fn main() {
let x = "Hi".to_string();
let y = &x; // y is a reference to x
let z = *y; // dereferencing y to get the value of x
println!("{}", z);
println!("{}", y);
println!("{}", x);
}Type
x: StringMemory
xlives on the stack- The actual string data (
"Hi") lives on the heap xowns the heap allocation
Ownership
Stringis notCopy- Ownership matters strictly
Type
y: &StringWhat this means
-
yis an immutable borrow ofx -
xstill owns theString -
The borrow checker enforces:
xcannot be mutably borrowed whileyexistsxcannot be moved whileyexists
So far, everything is valid.
This is the critical line.
- Dereference the reference
- Attempt to extract the value of type
String
So the compiler interprets this as:
let z: String = *y;- Dereferencing a reference to a non-
Copytype attempts a move - Moving a value out of a borrowed reference is forbidden
The compiler error (simplified) is:
cannot move out of
*ywhich is behind a shared reference
This is a compile-time error, not a runtime issue.
| Type | Implements Copy |
Dereferencing Behavior |
|---|---|---|
i32 |
Yes | Value is copied |
String |
No | Move attempted → error |
In your previous example:
let z = *y; // i32 → copiedHere:
let z = *y; // String → move (illegal)You cannot move ownership out of a shared reference (
&T).
This rule prevents:
- Use-after-free
- Double free
- Dangling pointers
If Rust allowed this:
let z = *y;Then:
zwould own theStringxwould be left in an invalid state- But
ystill exists and points tox
This would break Rust’s safety guarantees.
let z = y.clone();or
let z = (*y).clone();Now:
- Heap data is duplicated
- Ownership is clear
- Compiler is satisfied
let z = y; // z: &StringNo ownership transfer occurs.
If you only want to read the value:
println!("{}", y);
println!("{}", x);Rust automatically dereferences &String to &str.
-
println!needs something that implementsDisplay -
&Stringdoes not implementDisplay -
Rust automatically:
&String → String → &str -
This is deref coercion, not ownership transfer
No move happens here.
Stringis heap-allocated, non-Copy, ownership-based&Stringis a shared borrow*yattempts a move- Moving from a shared reference is illegal
- The compiler rejects this at compile time
-
*on&T:- Copies if
T: Copy - Moves if
Tis notCopy
- Copies if
-
Moves from
&Tare forbidden -
Cloning must be explicit
If you want, I can next:
- Rewrite this using
&strinstead ofString - Show the exact compiler error and decode it
- Compare this with C++
std::string& - Explain how
Dereftrait enables coercions here
Tell me how deep you want to go.