In Rust, every value has an owner, regardless of whether that value is stored on the heap or the stack. The ownership system treats them equally.
To clarify the differences and connections, I have organized the following table for you.
Ownership Comparison: Heap Data vs Stack Data
| Feature | Heap Data (e.g., <span>String</span>, <span>Vec<T></span>) |
Stack Data (e.g., <span>i32</span>, <span>char</span>) |
|---|---|---|
| Ownership Rules | Also follows the “one value, one owner” rule. | Also follows the “one value, one owner” rule. |
| Assignment/Parameter Passing Behavior | Move: Ownership is transferred by default, and the original variable becomes invalid. | Copy: Bitwise copying occurs by default, creating a complete new value, and the original variable remains valid. |
| Key Differences | Implements the <span>Drop</span> Trait, requiring custom cleanup logic (e.g., releasing memory). |
Implements the <span>Copy</span> Trait, indicating low copying cost. |
| Design Purpose | Manages dynamically sized, lifetime-uncertain data,avoiding “double free”. | Efficiently handles fixed-size simple data, with minimal copying overhead. |
Why Does the Illusion of “No Ownership for Stack Data” Arise?
It is natural to have this thought, mainly because of the default behaviors being different:
-
Heap Data: When assigning or passing parameters, the default is move semantics. You will immediately feel the presence of ownership because trying to use a moved variable will result in a compilation error. For example:
let s1 = String::from("hello"); // Heap data let s2 = s1; // Ownership moves from s1 to s2 // println!("{}", s1); // Error! s1 no longer owns the data -
Stack Data: When assigning or passing parameters, the default is copy semantics. Since a complete copy is created, the old and new variables do not affect each other, so you rarely encounter the issue of “variable becoming invalid after ownership transfer”. This allows the rules of ownership to operate silently and efficiently in the background. For example:
let x = 5; // Stack data let y = x; // The value 5 is copied to y println!("x is {}, y is {}", x, y); // Completely correct, both x and y are valid
Core Points Summary
Remember the following points to understand the relationship between ownership and storage location well:
- Ownership is a Universal Rule: Rust’s ownership rules apply to all values, regardless of storage location.
<span>Copy</span>and<span>Drop</span>are Key: The fundamental reason for behavioral differences lies in whether the type implements the<span>Copy</span>trait (for stack data) or the<span>Drop</span>trait (for heap data).- Consistent Goals: Whether moving or copying, the core goal is consistent—to ensure memory safety at compile time, avoiding dangling pointers and double frees.
Therefore, the accurate understanding is: The ownership system manages all data; it is just that stack data implements the <span>Copy</span> trait, making the transfer of ownership appear “silent” and cost-effective, which makes you feel unbound.