สรุปแนวทางการใช้งาน Ownership และ Borrowing ในภาษา Rust กันหน่อย
Ownership
โดยทั่วไปแล้วแนวคิด Ownership ใน Rust จะมองว่าทุกค่า (value) ใน Rust มีตัวแปรหนึ่งตัวที่เป็นเจ้าของ (owner) ของมัน ในเวลาใดเวลาหนึ่ง ค่า (value) จะมีเจ้าของเพียงตัวเดียวเท่านั้น เมื่อออกนอก scope ค่านั้นจะถูกทำลาย (drop) โดยอัตโนมัติ
กฎหลักของ Ownership
- ค่าจะมีเจ้าของเพียงตัวเดียวในเวลาใดเวลาหนึ่ง
- เมื่อเจ้าของ (ตัวแปร) ออกจาก scope ข้อมูลในหน่วยความจำจะถูกคืนอัตโนมัติ
- ไม่สามารถมีการ copy ของค่าที่เป็น heap โดยอัตโนมัติ (ต้องใช้
Clone
หรือCopy
trait)
ตัวอย่างเช่น
fn main() {
let s1 = String::from("Hello"); // s1 เป็นเจ้าของ String นี้
let s2 = s1; // Ownership ถูกย้าย (move) ไปที่ s2
// println!("{}", s1); // ❌ Error: s1 ไม่เป็นเจ้าของอีกต่อไป
println!("{}", s2); // ✅ ใช้ s2 ได้
}
String
เก็บข้อมูลใน Heap ดังนั้น เมื่อเรา let s2 = s1;
Rust จะ move ownership แทนการ copy เพื่อป้องกันการ free ซ้ำ
ถ้าอยากใช้ทั้ง s1 และ s2 ต้องใช้ clone()
fn main() {
let s1 = String::from("Hello");
let s2 = s1.clone(); // copy ข้อมูลจริงใน Heap
println!("s1 = {}, s2 = {}", s1, s2); // ใช้ได้ทั้งคู่
}
Borrowing
Borrowing ใน Rust คือ การ “ยืมค่า” จากเจ้าของ (owner) โดยไม่ย้าย ownership ไปยังตัวแปรอื่น จุดประสงค์ คือ ให้ฟังก์ชันหรือโค้ดส่วนอื่นเข้าถึงข้อมูลชั่วคราว โดยไม่ต้องโอนความเป็นเจ้าของ
กฎหลักของ Borrowing
- Immutable references (
&T
):
- ยืมค่า แบบอ่านอย่างเดียว
- ยืมได้หลายตัวพร้อมกัน (read-only, ไม่มีปัญหา data race)
- Mutable references (
&mut T
):
- ยืมค่า แบบแก้ไขได้
- ยืมได้ แค่หนึ่งตัวในเวลาเดียวกัน (เพื่อป้องกันการแก้ไขพร้อมกัน)
- ห้ามมี mutable borrow พร้อมกับ immutable borrow ในเวลาเดียวกัน
- เพื่อป้องกันการอ่านค่าที่กำลังถูกแก้ไข
ตัวอย่าง Borrow แบบอ่านอย่างเดียว (Immutable Borrow)
fn main() {
let s = String::from("hello");
print_length(&s); // ยืม s
println!("{}", s); // ✅ s ยังใช้ได้เพราะ ownership ไม่ถูก move
}
fn print_length(s: &String) {
println!("Length: {}", s.len());
}
สิ่งสำคัญ: s
ยังคงเป็นเจ้าของหลังจากส่ง &s
ให้ฟังก์ชัน
ตัวอย่าง Borrow แบบแก้ไข (Mutable Borrow)
fn main() {
let mut s = String::from("hello");
modify(&mut s); // ส่ง mutable reference
println!("{}", s); // ✅ s ยังเป็นเจ้าของ
}
fn modify(s: &mut String) {
s.push_str(" world");
}
สิ่งสำคัญ: ต้องประกาศ mut
ที่ตัวแปรด้วย (let mut s
) เพื่อเป็นการบอกว่าตัวแปรนี้ อนุญาตให้แก้ไขได้
ตัวอย่าง ห้ามมี mutable borrow พร้อมกับ immutable borrow
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
// let r2 = &mut s; // ❌ Error: มี mutable borrow ซ้ำ
println!("{}", r1);
}
หรือ
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
// let r3 = &mut s; // ❌ Error: immutable borrow + mutable borrow พร้อมกัน
println!("{}, {}", r1, r2);
}
นอกจากกฏหลัก 3 ข้อข้างบนแล้ว ยังมีรายละเอียดอื่นๆ อีกด้วย เช่น
Borrowing + Lifetimes: ตัวที่ Reference ต้อง มีอายุไม่ยาวกว่าเจ้าของ และ Rust จะตรวจสอบให้โดยใช้ lifetimes
Borrowing ป้องกัน Dangling Pointer (Dangling References) และ Data Race
fn main() {
let r;
{
let s = String::from("hello");
r = &s; // ❌ Error: s หมดอายุแล้ว แต่ r ยัง reference อยู่
}
println!("{}", r);
}
Ownership vs Borrowing
จริงๆ คิดว่าถ้าเข้าใจแต่ละตัวแล้ว เราน่าจะแยกออกแล้วว่า แต่ละตัวต่างกันยังไง แต่ขอสรุปไว้ให้สักหน่อย
Ownership (เจ้าของค่า)
- ใช้เมื่อเราต้องการ โอนสิทธิ์ความเป็นเจ้าของของค่า (move) ไปยังฟังก์ชันหรือโครงสร้างอื่น
- ทุกค่ามีเจ้าของ (owner) เพียงตัวเดียว
- เมื่อเจ้าของหมดอายุ (scope จบ) ค่าจะถูก free อัตโนมัติ
- เหมาะในกรณี:
- ค่าไม่ต้องใช้ที่ต้นทางอีก (move ไปเลย)
- ต้องการหลีกเลี่ยงการ copy ข้อมูลขนาดใหญ่ (ใช้ move แทน clone)
- ต้องการ transfer resource ownership อย่างปลอดภัย (เช่นเปิดไฟล์, network socket)
- ป้องกัน memory leak และ double free โดย design
Borrowing (การยืมค่า)
- ใช้เมื่อเราต้องการ ใช้ค่าที่มีอยู่โดยไม่ย้าย ownership
- ใช้
&
(reference) เพื่อยืมค่าแทนการย้าย (move) - มี 2 แบบ:Immutable borrow:
&T
(ยืมแบบอ่านได้เท่านั้น)Mutable borrow:&mut T
(ยืมแบบแก้ไขได้ แต่มีได้แค่ 1 ในเวลาเดียวกัน)ไม่สามารถมี mutable และ immutable borrow พร้อมกัน ได้ → ป้องกัน data race - Lifetimes ใช้เพื่อระบุอายุของ reference เพื่อให้แน่ใจว่า reference ยัง valid อยู่
เหมาะในกรณี:
- ต้องการอ่านค่าชั่วคราว โดยไม่เปลี่ยนแปลง (
&T
) - ต้องการแก้ไขค่าชั่วคราว โดยไม่ย้าย ownership (
&mut T
) - ต้องการส่งค่าหลายครั้งให้หลายฟังก์ชัน โดยไม่ copy
สถานการณ์ | ใช้ Ownership | ใช้ Borrowing |
---|---|---|
ส่งค่าขนาดใหญ่ไปฟังก์ชัน แล้วไม่ต้องใช้ที่เดิม | ✅ | ❌ |
ส่งค่าขนาดใหญ่ไปฟังก์ชัน แต่ยังต้องใช้ที่เดิม | ❌ | ✅ |
สร้าง object ใหม่ แล้วให้ struct เป็นเจ้าของ | ✅ | ❌ |
อ่านค่าจาก struct หลายครั้ง | ❌ | ✅ (immutable borrow) |
แก้ไขค่าผ่านฟังก์ชัน แต่ยังต้องใช้ต่อ | ❌ | ✅ (mutable borrow) |
สรุปไว้ประมาณนี้ ในการใช้งาน rust นั้น เราจะต้องคิดเรื่องการ manage ตัวแปร ให้มีประสิทธิภาพที่สุด