หนึ่งในจุดเด่นของภาษา Rust คือ การบริหารจัดการ memory ซึ่งโดยทั่วไปแล้ว ภาษาอื่นๆ ก็มีการบริหารจัดการ memory ให้อยู่แล้ว บางภาษาอาจจะใช้สิ่งที่เรียกว่า garbage collection ที่ทำหน้าที่มองหาตัวแปรที่ไม่ได้มีการใช้งานเป็นเวลานานแล้ว และจัดการคืน memory กลับไป แต่ในภาษา Rust จะมีกฏหรือของการใช้งาน memory เพิ่มเข้ามา และเราสามารถรู้ได้ในทันทีว่ามีตัวแปรไหนบ้างที่ไม่ได้ใช้งาน
บทความนี้เรามาลงรายละเอียดเกี่ยวกับเรื่องนี้กัน แต่ก่อนจะไปทำความเข้าใจเกี่ยวกับ ownership นั้น เรามีเรื่องที่ต้องเข้าใจกันก่อน นั่นคือ Stack และ Heam Memory Allocation ซึ่ง ไปอ่านทำความเข้าใจกันก่อนเลย

Variable Scope
เริ่มต้นกันด้วย scope ของตัวแปรกันก่อนเลย ตัวแปรในภาษา rust นั้นจะสามารถใช้งานได้เมื่อมันถูกประกาศขึ้นมา และใช้งานได้เฉพาะภายใน scope { ... }
นั้น เช่น
fn main() {// s จะยังไม่สามารถใช้งานได้ เนื่องจากยังไม่ถูกประกาศ
let number:i32 = 10; // s ถูกประกาศและสามารถใช้งานได้แล้ว
// number สามารถใช้งานได้ภายใน scope นี้
}
// เมื่อออกจาก scope แล้ว s จะไม่สามารถใช้ได้อีก
variable scope
เราอาจจะจำมันแบบง่ายๆ คือ มันจะใช้งานได้ ภายใต้ scope ที่มันถูกประกาศไว้นั่นเอง
ในกรณีที่เป็นตัวแปร data types ทั่วไปนั้น ค่าจะถูกเก็บลง stack ทำให้ง่ายต่อการค้นหาและจัดการกับมัน rust เลยอนุญาตให้เราสามารถ copy
ค่าไปยังตัวแปรใหม่ตัวไหนก็ได้ โดยที่ตัวแปรนั้นจะยังสามารถใช้งานได้อยู่
fn main() {
let x = 1; // copy only type on the stack such as integer, boolean etc.
let y = x;
println!("x: {:?}", x);
println!("y: {:?}", y);
}
copy value
หากเราส่งค่าไปยัง function
อื่น ตัวแปร x
และ y
ก็ยังสามารถใช้งานได้เหมือนเดิม
fn main() {
let x = 1;
let y = x;
make_copy(x);
println!("x: {}", x);
println!("y: {}", y);
}
fn make_copy(one: i32) {
println!("make_copy: {}", one);
}
copy to function
Ownership Rules
ในทางกลับกันหากตัวแปรนั้นถูกเก็บไว้ใน heap (เช่น String) มันจะยุ่งยากกว่าในการหาว่าข้อมูลไหนควรที่จะ clean up ออกไปเมื่อไม่มีการใช้งานแล้ว
ดังนั้น rust เลยสร้างกฏของการเป็นเจ้าของ (ownership) ไว้ดังนี้
- ค่าแต่ละตัวภายใน Rust จะต้องมีเจ้าของ
- ในการทำงานแต่ละครั้งค่าเหล่านั้นจะต้องมีเจ้าของแค่ตัวเดียว
- เมื่อออกจาก scope แล้ว ค่าหรือตัวแปรที่อยู่ใน scope จะถูกลบทิ้ง
กฏ 3 ข้อนี้ จะถูกนำเอามาใช้ในการเขียนโค๊ดของเรา ทีนี้เรามาดูตัวอย่างของ กฏ 3 ทั้งข้อนี้ กัน
Move
เริ่มต้นที่ทำความเข้าใจเรื่องของ ค่าแต่ละค่าจะต้องมีเจ้าของ และ ต้องมีเข้าของเพียงตัวเดียวเท่านั้น
fn main() {
let x = String::from("Nutshell");
let y = x;
println!("x: {:?}", x); // Error: value borrowed here after move
println!("y: {:?}", y);
}
value borrowed here after move
การ assign ค่าไปยังอีกตัวแปรหนึ่งที่ เก็บบน heap
นั่น เราจะเรียกมันว่า การ Move
ซึ่งต่างจากตัวแปรที่เก็บลงบน stack ที่ใช้ใช้การ copy
ข้อมูลแทน (แทนการเปลี่ยนเจ้าของ)
ดังนั้น เมื่อเราทำการ move
ค่าจากตัวแปร x
ไปยังตัวแปร y
แล้วนั้น เจ้าของของค่านั้นเปลี่ยนไปเป็น y
ในทันที ทำให้เราไม่สามารถเรียกใช้งาน x
ได้ ดังภาพ

ดังนั้นถ้าหากเราสั่ง cargo build
จะแสดงข้อความ error ขึ้นมา และแนะนำให้งาน clone
แทนการ move
ค่า
error[E0382]: borrow of moved value: `x`
--> src\main.rs:13:22
|
11 | let x = String::from("Nutshell"); // vec!["Nutshell".to_string()];
| - move occurs because `x` has type `String`, which does not implement the `Copy` trait
12 | let y = x;
| - value moved here
13 | println!("{:?}", x); // Error: value borrowed here after move
| ^ value borrowed here after move
|
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
|
12 | let y = x.clone();
| ++++++++
error message
Move to Function
ในกรณีเดียวกัน หากเราส่งค่าไปยัง function
อื่นแล้ว เราจะไม่สามารถใช้งานตัวแปรนั้นได้อีก ในตัวอย่าง ถ้าหากเราเรียกใช้งาน msg
หลังจากที่ส่งค่าไปยัง take_ownership
แล้ว มันจะแสดง error
ขึ้นมาเหมือนกัน
fn main() {
let msg = String::from("Hello"); // create a variable with a string "Hello"
takes_ownership(msg); // Give ownership to the function
println!("msg: {:?}", msg); // Error: value borrowed here after move
}
fn takes_ownership(some_string: String) {
println!("takes_ownership: {:?}", some_string);
}
move ownership to function
ดังนั้น หากยังต้องการใช้งาน msg
ต่อให้ใช้วิธีการ clone
หรือ Reference and borrow
แทน
เช่นเดียวกันกับการส่งค่ากลับมาจาก function
อื่น ก็เป็นการเปลี่ยนเจ้าของ (ownership
) จากตัวแปรหนึ่งไปอีกตัวแปรหนึ่ง
fn main() {
let msg: String = give_ownership();
println!("msg: {:?}", msg);
}
fn give_ownership() -> String {
let some_string = String::from("Given");
some_string
}
give ownership to msg
Clone
หากเราต้องการที่จะใช้งานตัวแปร x
อยู่ ให้ใช้วิธีการ clone
ข้อมูลนั้นให้แก่ตัวแปร y
แทน
fn main() {
let x = String::from("Nutshell");
let z = x.clone();
println!("x: {:?}", x);
println!("y: {:?}", z);
}
clone value from x to y
วิธีนี้เป็นการคัดลองข้อมูลอีกชุดนึงขึ้นมาแล้วให้ y
เก็บค่า pointer
ที่ชี้ไปที่ตัวใหม่ ดังภาพ

Reference and borrow
ถ้าหากเราต้องการส่งข้อมูลไปยังตัวแปรอื่น เพื่อทำงานบางอย่าง แต่ยังอยากใช้งานตัวแปรนั้นได้เหมือนเดิม ให้ใช้วิธีการอ้างอิง Reference ไปยังตัวแปรนั้นแทน ถ้าใครเคยเขียนภาษา C มันคือ pass by reference
นั่นเอง

ซึ่งการใช้งานจะใช้ &
ในการอ้างอิงค่า pointer ถ้าเป็นตัวแปรที่ส่งให้ใช้ &
หน้าตัวแปร (&s1
) และตัวแปรที่ใช้รับค่าจะใช้ &
หน้าประเภทแทน (s: &String
)
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{:?}' is {:?}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
Reference value
หาต้องการแก้ไขค่าในตัวแปรนั้นให้เพิ่ม mut
เข้าไปด้วย ทั้งในตัวแปรที่ส่ง ใส่ไว้หน้า &
แบบนี้ &mut s
และตัวแปรที่รับ ใส่ไว้หน้า type some_string: &mut String
และอย่าลืมใส่ mut
ตอนประกาศตัวแปรด้วยนะ
fn main() {
let mut s = String::from("Hello");
change_string(&mut s); // &s is a reference to s
println!("s: {}", s);
}
fn change_string(some_string: &mut String) { // add mut if you want to change the string
some_string.push_str(", world"); // `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
}
if change value in variable, add mut
after &
Slices
ส่วนอีกวิธีในการแก้ไขค่าในตัวแปร มีอีกวิธีหนึ่งที่นิยมใช้งานกัน คือ การ slices ข้อมูล อธิบายง่ายๆ วิธีนี้แทนที่เราจะ refer ไปยังข้อมูลทั้งหมด แต่เราจะเลือก refer ไปที่ข้อมูลบางส่วนแทน ซึ่งจะทำให้การเข้าถึงและการแก้ไขข้อมูลนั้นเร็วขึ้นไปอีก

โดยเราจะใช้ &s
เพื่ออ้างอิงตำแหน่ง address ของข้อมูลที่เก็บค่าไว้ พร้อมทั่งระบุ range ที่ต้องการ slices ด้วย [0..5]
fn main() {
let s = String::from("hello world");
// Slices
let hello = &s[0..5];
let world = &s[6..11];
println!("{} {}", hello, world);
}
String Slices
เราสามารถระบุ range ได้หลายแบบ ดังนี้
- ระบุเริ่มต้นที่ 0 และ n:
[0..5]
หรือ[..2]
- จุดเริ่มต้นที่ n ถึงจุดที่สิ้นสุด (m) แบบ fixed:
[2..5]
- จุดเริ่มต้นที่ n ถึงจุดที่สิ้นสุด (m) แบบไม่ dynamic :
let len = s.len();
->[3..len]
หรือ[3..]
- ระบุทั้งหมด:
[0..len]
หรือ[..]