Rust: กลไก Move Ownership ที่ช่วยให้จัดการหน่วยความจำได้อย่างปลอดภัย

Weerasak Chongnguluam
May 25 · 2 min read

จากโพสต์ก่อนหน้านี้อธิบายไปแล้วว่า Rust จัดการคืนหน่วยความจำที่ไม่ใช้แล้ว โดยไม่ได้ใช้ GC (Garbage Collector) แต่จะทำการคืนหน่วยความจำก็ต่อเมื่อ scope ของตัวแปรที่เป็นเจ้าของหน่วยความจำนั้น (owner) จบการทำงานลง

ในเรื่องของ Ownership เรามาดูกันอีกครั้งว่าเมื่อเราสร้างตัวแปร String ขึ้นมาแบบนี้

fn main() {
let name = String::from("John");
println!("{}", name);
}

name เป็นตัวแปรที่เป็นเจ้าของค่า String::from("John")

Move Ownership

กฎเรื่อง Ownership ของ Rust นั้นมีอยู่อย่างนึงว่า ค่าใดๆ จะมีตัวแปรที่เป็นเจ้าของ (owner) ได้แค่ตัวแปรเดียวเท่านั้น และ เราสามารถเปลี่ยนเจ้าของเป็นตัวแปรอื่นได้ นั่นแหละคือการ Move Ownership เช่น

fn main() {
let name = String::from("John");
println!("{}", name);
let first_name = name;
println!("{}", first_name);
}

สำหรับภาษาอื่นตรง let first_name = name; นั่นก็คือการกำหนดค่า first_name ให้เท่ากับ name แต่สำหรับ Rust มันคือการเปลี่ยนเจ้าของ สิ่งที่เกิดขึ้นกับ name หลังจากโอนความเป็นเจ้าของค่า String::from("john") ไปให้กับ first_name แล้วก็คือ จะถูกใช้งานใน scope นี้ไม่ได้อีกต่อไป ถ้าใช้งาน compiler จะ compile ไม่ผ่านแล้วแจ้งเราแบบนี้

fn main() {
let name = String::from("John");
println!("{}", name);
let first_name = name;
println!("{}", first_name);
println!("{}", name);
}
Compiling playground v0.0.1 (/playground)
error[E0382]: borrow of moved value: `name`
--> src/main.rs:6:20
|
2 | let name = String::from("John");
| ---- move occurs because `name` has type `std::string::String`, which does not implement the `Copy` trait
3 | println!("{}", name);
4 | let first_name = name;
| ---- value moved here
5 | println!("{}", first_name);
6 | println!("{}", name);
| ^^^^ value borrowed here after move

error: aborting due to previous error

For more information about this error, try `rustc --explain E0382`.
error: Could not compile `playground`.

To learn more, run the command again with --verbose.

สำหรับคำว่า borrowed ขอติดไว้ก่อน เอาเป็นว่าสิ่งทีเกิดขึ้นกับ name คือเราเอาไปใช้ต่อไม่ได้แล้ว เพราะได้โอนถ่ายความเป็นเจ้าของไปให้คนอื่นแล้วนั่นเอง

Move Semantic ป้องกันการใช้หน่วยความจำที่ถูกคืนไปแล้ว

เหตุผลที่ Rust ไม่ยอมให้ใช้ตัวแปรที่ถูก move ownership ไปให้ตัวแปรอื่นแล้วเพราะ ถ้าเกิดเรา move ไปให้ตัวแปรที่อยู่อีก scope แล้วตัวแปรนั้นจบการทำงานก่อน จะเกิดปัญหาการใช้งานหน่วยความจำที่คืนไปแล้ว เช่น

fn main() {
let name = String::from("John");
println!("{}", name);
{
let first_name = name;
println!("{}", first_name);
} // ตัวแปร first_name คืนหน่วยความจำเมื่อจบ scope นี้
// ตัวแปร name ถ้าไม่มี move semantic จะใช้หน่วยความจำที่คืนแล้ว
println!("{}", name);
} // และจำทำให้เกิดการคืนหน่วยความจำซ้ำซ้อนตรงนี้

ตัวแปร first_name จะจบการทำงานก่อนแล้วคืนหน่วยความจำไป แล้วถ้า Rust ไม่ป้องกันก็จะทำให้ name อ้างถึงหน่วยความจำที่คืนไปแล้ว แล้วยังมีโอกาสที่จะคืนหน่วยความจำซ้ำซ้อนอีกต่างหาก

Rust อาศัยกลไก Move Ownership ช่วยทำให้ Compiler ตรวจสอบการใช้งานตรงนี้ได้ตั้งแต่ compile และไม่ยอมให้มีการใช้หน่วยความจำที่ไม่ปลอดภัยแบบนี้เกิดขึ้นตอน runtime

ในครั้งต่อๆไปเราค่อยว่ากันด้วยเรื่อง Reference และ Borrow

Weerasak Chongnguluam

Written by

I’m a Programmer.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade