SyntaxStudy
Sign Up
Rust The Ownership Rules
Rust Beginner 1 min read

The Ownership Rules

Ownership is the central feature that makes Rust both safe and fast. There are three rules: every value has exactly one owner, there can only be one owner at a time, and when the owner goes out of scope the value is dropped. These rules are enforced entirely at compile time — there is no runtime garbage collector, reference counting overhead, or manual `free` call required. When a value stored on the heap (like a `String`) is assigned to another variable, the original binding is moved — it is no longer valid. This prevents double-free errors. Types that implement the `Copy` trait (integers, booleans, floats, char, and tuples of `Copy` types) are duplicated bitwise on assignment, so the original remains valid after the assignment. Dropping happens automatically when a variable leaves its scope. For types that own heap resources, Rust calls the `drop` function — analogous to a destructor in C++. You can implement the `Drop` trait on your own types to run custom cleanup logic. The combination of deterministic drop and single ownership means resource management in Rust is explicit, predictable, and free from leaks in safe code.
Example
fn main() {
    // --- Stack-allocated Copy type ---
    let a: i32 = 5;
    let b = a;          // 'a' is COPIED; both a and b are valid
    println!("a={a}  b={b}");

    // --- Heap-allocated String: MOVE semantics ---
    let s1 = String::from("hello");
    let s2 = s1;        // s1 is MOVED into s2; s1 is no longer valid
    // println!("{s1}"); // <-- compile error: value borrowed after move
    println!("s2 = {s2}");

    // --- Clone to make an explicit deep copy ---
    let s3 = String::from("world");
    let s4 = s3.clone();
    println!("s3={s3}  s4={s4}");  // both valid after clone

    // --- Ownership and functions ---
    let greeting = String::from("hi");
    takes_ownership(greeting);
    // println!("{greeting}"); // compile error: moved into function

    let num = 42i32;
    makes_copy(num);
    println!("num still valid: {num}"); // ok — i32 is Copy

    // --- Returning ownership ---
    let returned = gives_ownership();
    println!("returned = {returned}");
} // 's2', 's4', 'returned' are dropped here

fn takes_ownership(s: String) {
    println!("took ownership of: {s}");
} // s is dropped here

fn makes_copy(n: i32) {
    println!("copy of: {n}");
}

fn gives_ownership() -> String {
    String::from("transferred")
}