Skip to content

Ownership - The Law Core Concept

Mỗi byte trong Rust đều có chủ — và chỉ có MỘT chủ duy nhất

Ba Quy Tắc Vàng của Ownership

📜 THE THREE RULES

  1. Mỗi value trong Rust có một variable gọi là owner
  2. Chỉ có thể có MỘT owner tại một thời điểm
  3. Khi owner ra khỏi scope, value bị dropped (deallocated)

Đây là luật. Không có exceptions. Compiler enforce điều này tại compile-time.


Rule #1: Mỗi Value có Một Owner

rust
fn main() {
    let s = String::from("hello");  // s là owner của String "hello"
    //  ↑
    //  OWNER
}
                    STACK                              HEAP
               ┌─────────────────┐            ┌─────────────────────┐
     s: String │ ptr ──────────────────────▶  │ h │ e │ l │ l │ o │
      (OWNER)  ├─────────────────┤            └─────────────────────┘
               │ len: 5          │
               ├─────────────────┤
               │ cap: 5          │
               └─────────────────┘

Rule #2: Chỉ Một Owner Tại Một Thời Điểm (Move Semantics)

Code SAI: Sử dụng sau khi Move

rust
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;  // Ownership MOVED từ s1 sang s2
    
    println!("{}", s1);  // ❌ COMPILE ERROR!
}

Compiler Error:

error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:5:20
  |
2 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`
3 |     let s2 = s1;
  |              -- value moved here
4 |     
5 |     println!("{}", s1);
  |                    ^^ value borrowed here after move

Move Semantics Visualization

TRƯỚC let s2 = s1:
═══════════════════════════════════════════════════════════════════

                    STACK                              HEAP
               ┌─────────────────┐            ┌─────────────────────┐
    s1: String │ ptr ──────────────────────▶  │ h │ e │ l │ l │ o │
      (OWNER)  ├─────────────────┤            └─────────────────────┘
               │ len: 5          │
               │ cap: 5          │
               └─────────────────┘


SAU let s2 = s1:
═══════════════════════════════════════════════════════════════════

                    STACK                              HEAP
               ┌─────────────────┐            
    s1: String │ ██ INVALID ████ │            ┌─────────────────────┐
   (MOVED OUT) │ █████████████████│      ┌───▶│ h │ e │ l │ l │ o │
               │ █████████████████│      │    └─────────────────────┘
               └─────────────────┘      │

               ┌─────────────────┐      │
    s2: String │ ptr ────────────────────┘
   (NEW OWNER) ├─────────────────┤
               │ len: 5          │
               │ cap: 5          │
               └─────────────────┘

Code ĐÚNG: Clone nếu cần cả hai

rust
fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();  // Deep copy - s2 có data riêng
    
    println!("s1: {}, s2: {}", s1, s2);  // ✅ OK
}
SAU let s2 = s1.clone():
═══════════════════════════════════════════════════════════════════

                    STACK                              HEAP
               ┌─────────────────┐            ┌─────────────────────┐
    s1: String │ ptr ──────────────────────▶  │ h │ e │ l │ l │ o │
      (OWNER)  ├─────────────────┤            └─────────────────────┘
               │ len: 5          │
               │ cap: 5          │
               └─────────────────┘
                                              ┌─────────────────────┐
               ┌─────────────────┐      ┌────▶│ h │ e │ l │ l │ o │
    s2: String │ ptr ────────────────────┘    └─────────────────────┘
      (OWNER)  ├─────────────────┤              (COPY của data)
               │ len: 5          │
               │ cap: 5          │
               └─────────────────┘

Rule #3: Owner Ra Khỏi Scope → Value Dropped

rust
fn main() {
    {
        let s = String::from("hello");  // s valid từ đây
        // ... sử dụng s
    }  // ← s ra khỏi scope, String::drop() được gọi
       //   Heap memory được deallocate
    
    // println!("{}", s);  // ❌ ERROR: s không tồn tại
}

Drop được gọi tự động

rust
fn main() {
    let s = String::from("hello");
    
    // ... code ở đây
    
}  // ← Rust tự động gọi drop(s)
   //   Tương đương: s.drop() được compiler insert

So sánh với C++:

cpp
// C++ - phải nhớ delete
char* s = new char[6];
strcpy(s, "hello");
// ... quên delete s → MEMORY LEAK

Move trong Function Calls

Code SAI: Sử dụng sau khi pass vào function

rust
fn takes_ownership(s: String) {
    println!("{}", s);
}  // s dropped ở đây

fn main() {
    let s = String::from("hello");
    takes_ownership(s);  // Ownership MOVED vào function
    
    println!("{}", s);   // ❌ COMPILE ERROR!
}

Compiler Error:

error[E0382]: borrow of moved value: `s`
 --> src/main.rs:9:20
  |
6 |     let s = String::from("hello");
  |         - move occurs because `s` has type `String`
7 |     takes_ownership(s);
  |                     - value moved here
8 |     
9 |     println!("{}", s);
  |                    ^ value borrowed here after move

Stack Visualization

TRƯỚC takes_ownership(s):
═══════════════════════════════════════════════════════════════════

    main() Stack Frame:
    ┌─────────────────┐            ┌─────────────────────┐
    │ s: String       │───────────▶│ h │ e │ l │ l │ o │
    │   (OWNER)       │            └─────────────────────┘
    └─────────────────┘


SAU takes_ownership(s) được gọi:
═══════════════════════════════════════════════════════════════════

    takes_ownership() Stack Frame:    
    ┌─────────────────┐            ┌─────────────────────┐
    │ s: String       │───────────▶│ h │ e │ l │ l │ o │
    │   (NEW OWNER)   │            └─────────────────────┘
    └─────────────────┘

    main() Stack Frame:
    ┌─────────────────┐
    │ s: ██ INVALID██ │
    │   (MOVED OUT)   │
    └─────────────────┘

Code ĐÚNG: Return ownership về

rust
fn takes_and_gives_back(s: String) -> String {
    println!("{}", s);
    s  // Return ownership về caller
}

fn main() {
    let s1 = String::from("hello");
    let s2 = takes_and_gives_back(s1);  // s1 moved in, ownership returned to s2
    
    println!("{}", s2);  // ✅ OK
}

Copy Trait vs Drop Trait

Copy Types (Stack-only, Cheap to Copy)

Một số types implement Copy trait → assignment = bitwise copy, không move:

rust
fn main() {
    let x: i32 = 5;
    let y = x;  // x được COPY (không phải move)
    
    println!("x: {}, y: {}", x, y);  // ✅ CẢ HAI đều valid
}

Types implement Copy:

TypeSizeLý do Copy
Tất cả integers (i32, u64, etc.)FixedStack-only, copy nhanh
Tất cả floats (f32, f64)FixedStack-only, copy nhanh
bool1 byteTrivial
char4 bytesTrivial
Tuples chứa Copy typesFixed(i32, f64) là Copy
Arrays chứa Copy typesFixed[i32; 5] là Copy
References (&T)8 bytesPointer copy, không own data

Drop Types (Heap-involved, Move Semantics)

Types không implement Copy → assignment = Move:

TypeLý do Move
StringOwns heap data
Vec<T>Owns heap data
Box<T>Owns heap data
HashMap<K, V>Owns heap data
Custom structs (default)May contain Drop types

Quy tắc: Copy và Drop loại trừ nhau

rust
// ❌ KHÔNG THỂ implement cả hai
// Nếu type có destructor (Drop), không thể Copy

struct MyString {
    data: String,  // String là Drop → MyString cũng là Drop
}

// MyString tự động là Move, không phải Copy

Cách implement Copy cho custom type

rust
// ✅ Copy nếu tất cả fields đều Copy
#[derive(Copy, Clone)]
struct Point {
    x: i32,  // Copy
    y: i32,  // Copy
}

fn main() {
    let p1 = Point { x: 5, y: 10 };
    let p2 = p1;  // Copy, không phải Move
    
    println!("p1: ({}, {})", p1.x, p1.y);  // ✅ OK
    println!("p2: ({}, {})", p2.x, p2.y);  // ✅ OK
}
rust
// ❌ KHÔNG THỂ Copy nếu có field là Drop
#[derive(Copy, Clone)]  // ❌ COMPILE ERROR!
struct Person {
    name: String,  // String không phải Copy
    age: u32,
}

Compiler Error:

error[E0204]: the trait `Copy` may not be implemented for this type
 --> src/main.rs:1:10
  |
1 | #[derive(Copy, Clone)]
  |          ^^^^
2 | struct Person {
3 |     name: String,
  |     ------------ this field does not implement `Copy`
}

Memory Layout: Copy vs Move

Copy (Bitwise Duplicate)

rust
let x: i32 = 42;
let y = x;  // Copy
TRƯỚC:                          SAU (Copy):
┌────────────────┐              ┌────────────────┐
│ x: i32 = 42    │              │ x: i32 = 42    │  ← Vẫn valid
└────────────────┘              └────────────────┘
                                ┌────────────────┐
                                │ y: i32 = 42    │  ← Copy mới
                                └────────────────┘

Move (Ownership Transfer)

rust
let s1 = String::from("hello");
let s2 = s1;  // Move
TRƯỚC:                          SAU (Move):
┌────────────────┐              ┌────────────────┐
│ s1: ptr ───────┼──▶ "hello"   │ s1: INVALID    │  ← Không còn valid
└────────────────┘              └────────────────┘
                                ┌────────────────┐
                                │ s2: ptr ───────┼──▶ "hello"  (cùng data)
                                └────────────────┘

Ownership trong Pattern Matching

Move trong match

rust
fn main() {
    let opt: Option<String> = Some(String::from("hello"));
    
    match opt {
        Some(s) => println!("Got: {}", s),  // s takes ownership
        None => println!("Nothing"),
    }
    
    // println!("{:?}", opt);  // ❌ ERROR: opt moved in match
}

Avoid move với ref

rust
fn main() {
    let opt: Option<String> = Some(String::from("hello"));
    
    match &opt {  // Borrow, không move
        Some(s) => println!("Got: {}", s),  // s là &String
        None => println!("Nothing"),
    }
    
    println!("{:?}", opt);  // ✅ OK - opt không bị move
}

Bảng Tóm tắt

ConceptÝ nghĩa
OwnerVariable sở hữu value, chịu trách nhiệm drop
MoveChuyển ownership, biến cũ invalid
CopyBitwise duplicate, cả hai biến valid
DropDestructor, cleanup khi owner ra khỏi scope
CloneExplicit deep copy, tốn resource

Bài tập: Fix Compiler Errors

💪 Bài 1: Fix lỗi move
rust
fn main() {
    let s = String::from("hello");
    let len = calculate_length(s);
    println!("The length of '{}' is {}.", s, len);
}

fn calculate_length(s: String) -> usize {
    s.len()
}
Đáp án

Cách 1: Return ownership

rust
fn calculate_length(s: String) -> (String, usize) {
    let len = s.len();
    (s, len)
}

fn main() {
    let s = String::from("hello");
    let (s, len) = calculate_length(s);
    println!("The length of '{}' is {}.", s, len);
}

Cách 2: Dùng reference (tốt hơn - sẽ học ở bài sau)

rust
fn calculate_length(s: &String) -> usize {
    s.len()
}

fn main() {
    let s = String::from("hello");
    let len = calculate_length(&s);
    println!("The length of '{}' is {}.", s, len);
}
💪 Bài 2: Copy hay Move?

Xác định mỗi biến sau khi assignment là Copy hay Move:

rust
let a: i32 = 5;
let b = a;        // A

let s1 = String::from("hi");
let s2 = s1;      // B

let t1 = (1, 2);
let t2 = t1;      // C

let v1 = vec![1, 2, 3];
let v2 = v1;      // D
Đáp án
  • A: Copyi32 implements Copy
  • B: MoveString không implement Copy
  • C: Copy — Tuple của Copy types cũng Copy
  • D: MoveVec<T> không implement Copy