Mutable Borrows
การ Mutate ผ่าน Reference
หัวข้อที่มีชื่อว่า “การ Mutate ผ่าน Reference”shared reference &T เป็นแบบ read-only เมื่อคุณต้องการแก้ไขค่าที่ borrow มา ให้ใช้ mutable reference: &mut T
เงื่อนไขสองข้อต้องเป็นจริงทั้งคู่:
- ตัวแปรเดิมต้องถูกประกาศด้วย
mut - ตัว reference เองต้องเขียนเป็น
&mut
fn append_world(s: &mut String) { s.push_str(", world");}
fn main() { let mut greeting = String::from("hello"); append_world(&mut greeting); println!("{}", greeting); // hello, world}กฎการเข้าถึงแบบ Exclusive
หัวข้อที่มีชื่อว่า “กฎการเข้าถึงแบบ Exclusive”borrow checker บังคับ invariant ข้อเดียว:
ณ จุดใด ๆ ในโค้ด คุณอาจมี อย่างใดอย่างหนึ่ง ระหว่าง shared reference จำนวนเท่าไรก็ได้ หรือ mutable reference เพียง หนึ่งตัว ไปยังค่าหนึ่ง — แต่ไม่มีทั้งสองอย่างพร้อมกัน
กฎนี้ป้องกัน data races ตอน compile time data race เกิดขึ้นเมื่อ thread ตั้งแต่สองตัวขึ้นไปเข้าถึงหน่วยความจำเดียวกันพร้อมกัน และอย่างน้อยหนึ่งการเข้าถึงเป็นการเขียน ด้วยการทำให้การเข้าถึงแบบ mutable พร้อมกันกลายเป็น compile error Rust จึงกำจัดบั๊กประเภท data race ทั้งหมดออกไป — แม้ในโค้ดแบบ single-threaded กฎเดียวกันนี้ก็ช่วยให้ตรรกะถูกต้องสมเหตุสมผล
// THIS DOES NOT COMPILE — shown for teaching only://// fn main() {// let mut s = String::from("hello");// let r1 = &s; // shared borrow// let r2 = &mut s; // error[E0502]: cannot borrow `s` as mutable// // because it is also borrowed as immutable// println!("{} {}", r1, r2);// }Non-Lexical Lifetimes (NLL)
หัวข้อที่มีชื่อว่า “Non-Lexical Lifetimes (NLL)”ตั้งแต่ Rust 2018 เป็นต้นมา คอมไพเลอร์ใช้ Non-Lexical Lifetimes (NLL): borrow จะสิ้นสุดที่จุดสุดท้ายที่ถูกใช้งานจริง ไม่ใช่ที่ตอนจบของ block ที่ครอบอยู่ นั่นหมายความว่าคุณสามารถเริ่ม borrow ใหม่ได้อย่างปลอดภัยหลังจากการใช้งานครั้งสุดท้ายของตัวก่อนหน้า แม้จะอยู่ใน scope เดียวกันก็ตาม
fn main() { let mut s = String::from("hello");
let r1 = &mut s; r1.push_str(", world"); // last use of r1 // r1's borrow ends here (NLL)
let r2 = &s; // perfectly fine — r1 is gone println!("{}", r2);}Mutable References กับพารามิเตอร์ของฟังก์ชัน
หัวข้อที่มีชื่อว่า “Mutable References กับพารามิเตอร์ของฟังก์ชัน”การส่ง &mut T เข้าไปในฟังก์ชันบ่งบอกว่าฟังก์ชันนั้น อาจ แก้ไขค่าได้ ทำให้การ mutate ชัดเจนและมองเห็นได้ตรงจุดที่เรียก — คุณเห็น &mut ได้ทุกจุดที่ค่าอาจเปลี่ยนแปลง
fn double(n: &mut i32) { *n *= 2; // dereference to modify the value behind the reference}
fn main() { let mut value = 7; double(&mut value); println!("{}", value); // 14}สังเกต operator dereference *n: ในการแก้ไขค่าที่ mutable reference ชี้ไป คุณต้อง dereference ก่อน สำหรับ method ของ String อย่าง .push_str() Rust จะ dereference ให้อัตโนมัติผ่านฟีเจอร์ที่เรียกว่า auto-deref
fn append_world(s: &mut String) { s.push_str(", world");}
fn main() { let mut greeting = String::from("hello"); println!("before: {}", greeting); append_world(&mut greeting); println!("after: {}", greeting);
// One mutable borrow at a time let r1 = &mut greeting; r1.push_str("!"); println!("mutated: {}", r1); // r1 is no longer used here (NLL ends the borrow)
let r2 = &greeting; // shared borrow is fine now println!("shared: {}", r2);}Compiling…