ข้ามไปยังเนื้อหา

Mutable Borrows

shared reference &T เป็นแบบ read-only เมื่อคุณต้องการแก้ไขค่าที่ borrow มา ให้ใช้ mutable reference: &mut T

เงื่อนไขสองข้อต้องเป็นจริงทั้งคู่:

  1. ตัวแปรเดิมต้องถูกประกาศด้วย mut
  2. ตัว 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
}

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);
// }

ตั้งแต่ 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);
}

การส่ง &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);
}
syntax ใดที่สร้าง mutable reference ไปยังตัวแปรชื่อ data?
mutable reference ไปยังค่าเดียวกันสามารถมีอยู่พร้อมกันได้กี่ตัว?
คุณสามารถถือ shared reference และ mutable reference ไปยังค่าเดียวกันพร้อมกันได้ไหม?
กฎการเข้าถึงแบบ exclusive ของ &mut T ป้องกันปัญหาตอน compile-time ข้อใด?