Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Ownership คืออะไร

กฎ 3 ข้อของ Ownership

จำกฎ 3 ข้อนี้ให้ขึ้นใจ:

  1. ทุกค่ามีเจ้าของ (owner) หนึ่งเดียว
  2. ในเวลาใดก็ตาม ค่าหนึ่งมีได้แค่เจ้าของเดียว
  3. เมื่อเจ้าของออกจาก scope ค่าจะถูก drop (ลบ)

⚠️ คำเตือน: ข้อผิดพลาดที่พบบ่อย

  • ❌ ใช้ตัวแปรหลัง move
  • ❌ ส่ง ownership เข้า function แล้วพยายามใช้ต่อ
  • ❌ สร้าง multiple mutable references
  • ✅ ใช้ .clone() เมื่อต้องการ copy จริงๆ
  • ✅ ใช้ references (&) แทน move เมื่อไม่จำเป็นต้องโอน ownership

🔄 Ownership Flow Diagram

+-------------------------------------------------------------------+
|                  Ownership Flow Visualization                     |
+-------------------------------------------------------------------+
|                                                                   |
|   let s1 = String::from("hello");                                 |
|       |                                                           |
|       v                                                           |
|   +-------+                                                       |
|   |  s1   | <-- owner of "hello"                                  |
|   +---+---+                                                       |
|       |                                                           |
|       |  let s2 = s1;  (MOVE)                                     |
|       |                                                           |
|       v                                                           |
|   +-------+     +-------+                                         |
|   |  s1   | --> |  s2   | <-- new owner                           |
|   | DEAD  |     |  OK   |                                         |
|   +-------+     +-------+                                         |
|   (invalid)     (valid)                                           |
|                                                                   |
+-------------------------------------------------------------------+
|  Alternatives:                                                    |
|  1. CLONE: let s2 = s1.clone();  -> s1 OK, s2 OK (both copies)    |
|  2. BORROW: let s2 = &s1;        -> s1 OK, s2 OK (temporary ref)  |
+-------------------------------------------------------------------+

ตัวอย่างพื้นฐาน

fn main() {
    {
        let s = String::from("hello"); // s เกิดขึ้น เป็น owner ของ "hello"

        println!("{}", s); // ใช้ s ได้

    } // s ออกจาก scope -> memory ถูก free อัตโนมัติ

    // println!("{}", s); // ❌ Error! s ไม่มีแล้ว
}

Stack vs Heap

เพื่อเข้าใจ Ownership ต้องเข้าใจ Stack และ Heap ก่อน:

Stack

  • เก็บข้อมูลขนาดคงที่
  • เร็วมาก
  • LIFO (Last In, First Out)
  • เช่น: i32, f64, bool, char

Heap

  • เก็บข้อมูลขนาดไม่คงที่
  • ช้ากว่า Stack
  • ต้อง allocate และ deallocate
  • เช่น: String, Vec<T>
+-----------------+
|     Stack       |
+-----------------+
| ptr  ----------+       +---------------+
| len = 5        |       |     Heap      |
| capacity = 5   |       +---------------+
+----------------+------>| h e l l o     |
                         |               |
                         +---------------+

Copy Types vs Move Types

Copy Types (อยู่บน Stack)

ข้อมูลขนาดเล็กถูก copy อัตโนมัติ:

fn main() {
    let x = 5;
    let y = x; // copy ค่า

    println!("x = {}, y = {}", x, y); // ✅ ทั้งสองใช้ได้
}
Copy Types
i32, u32, i64, etc.
f32, f64
bool
char
Tuples ที่มี Copy types เท่านั้น

Move Types (อยู่บน Heap)

ข้อมูลบน Heap ถูก move:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1 ถูก MOVE ไป s2

    // println!("{}", s1); // ❌ Error! s1 ถูก move ไปแล้ว
    println!("{}", s2); // ✅ OK
}

ทำไมต้อง Move?

ถ้า Rust copy ข้อมูลบน Heap แทนที่จะ move:

        +-------------+
s1 ---->|   hello     |<---- s2 (if copy)
        +-------------+

When s1 and s2 go out of scope
-> Both try to free the same memory
-> DOUBLE FREE!

Rust ป้องกันโดยทำให้ s1 ใช้ไม่ได้หลัง move


Ownership และ Functions

ส่งค่าเข้า function = Move

fn main() {
    let s = String::from("hello");

    takes_ownership(s); // s ถูก move เข้า function

    // println!("{}", s); // ❌ Error! s ถูก move ไปแล้ว
}

fn takes_ownership(some_string: String) {
    println!("{}", some_string);
} // some_string ถูก drop ที่นี่

Return = ย้าย ownership กลับ

fn main() {
    let s1 = gives_ownership(); // s1 ได้รับ ownership

    println!("{}", s1); // ✅ OK
}

fn gives_ownership() -> String {
    let s = String::from("hello");
    s // return และ move ownership
}

ตัวอย่าง: ส่งและรับกลับ

fn main() {
    let s1 = String::from("hello");

    let (s2, len) = calculate_length(s1);

    println!("The length of '{}' is {}.", s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len();
    (s, length) // return tuple
}

ปัญหา: ต้อง return ค่ากลับมาถ้าต้องการใช้ต่อ ยุ่งยากมาก!

ทางออก: ใช้ References (บทถัดไป)


Scope และ Drop

fn main() {
    let outer = String::from("outer");

    {
        let inner = String::from("inner");
        println!("{}", inner); // ✅ OK
    } // inner ถูก drop ที่นี่

    // println!("{}", inner); // ❌ inner ไม่มีแล้ว

    println!("{}", outer); // ✅ OK
} // outer ถูก drop ที่นี่

ลองทำดู! 🎯

  1. ลองเขียนโค้ดที่ move String และดู error
  2. แก้ไขโดยใช้ .clone()
  3. ลองส่ง String เข้า function และดูว่าเกิดอะไร

🧠 Advanced: Ownership Edge Cases

Case 1: Partial Move

struct Person {
    name: String,
    age: u32,
}

fn main() {
    let person = Person {
        name: String::from("Alice"),
        age: 30,
    };
    
    let name = person.name;  // move name ออกจาก struct
    // println!("{}", person.name);  // ❌ Error! name ถูก move แล้ว
    println!("{}", person.age);      // ✅ OK! age ยังอยู่ (Copy type)
}

Case 2: Reference ใน Struct

// ❌ ไม่ได้! struct เก็บ reference ต้องมี lifetime
struct BadStruct {
    data: &str,  // Error: missing lifetime specifier
}

// ✅ ถูกต้อง
struct GoodStruct<'a> {
    data: &'a str,
}

Case 3: เมื่อใดใช้ Clone vs Reference

สถานการณ์ใช้เหตุผล
ข้อมูลเล็ก ใช้หลายที่.clone()overhead น้อย
ข้อมูลใหญ่ อ่านอย่างเดียว&Tประหยัด memory
ข้อมูลใหญ่ ต้อง modify&mut T หรือ moveขึ้นกับ use case
ส่งข้าม threadsArc<T> + cloneshared ownership

📝 ทดสอบความเข้าใจ

Q1: ทำไม i32 copy ได้แต่ String copy ไม่ได้?

A: i32 เก็บบน stack มีขนาดคงที่ copy ได้เร็ว ส่วน String เก็บ pointer ไปยัง heap ถ้า copy แบบ shallow ทั้งสองตัวแปรจะชี้ไปที่ memory เดียวกัน เกิด double free

Q2: ถ้าต้องการให้หลายตัวแปร "เป็นเจ้าของ" ข้อมูลเดียวกัน ทำยังไง?

A: ใช้ Rc<T> (single thread) หรือ Arc<T> (multi-thread) เพื่อ shared ownership โดยนับ references

Q3: เมื่อไหร่ควรใช้ .clone() vs borrow?

A:

  • Clone: เมื่อ data เล็ก หรือต้องการ ownership แยกจริงๆ
  • Borrow: เมื่อ data ใหญ่ หรือแค่ต้องการอ่าน/เขียนชั่วคราว

สรุป

แนวคิดคำอธิบาย
Ownerตัวแปรที่ “เป็นเจ้าของ” ค่า
Moveย้าย ownership
Dropลบข้อมูลเมื่อออกจาก scope
Stackข้อมูลขนาดเล็ก copy ได้
Heapข้อมูลขนาดใหญ่ต้อง move

👉 ต่อไป: Move & Clone