روی یک برنامهی C یا ++C، دسترسی به حافظهای که دیگر معتبر نیست معمولاً چیزی است که در تست هم رد میشود و ماهها بعد، در پروداکشن، به یک segfault یا رفتار نامشخص تبدیل میشود. در Rust همان اشتباه، همان لحظه که کامپایل میکنی، با یک پیام خطای مشخص متوقفت میکند — نه در رانتایم، نه در پروداکشن.
مثال اول: مقداری که «جابهجا» شده
rustfn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
}
error[E0382]: borrow of moved value: `s1`
--> main.rs:4:28
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s1;
| -- value moved here
4 | println!("{}, world!", s1);
| ^^ value borrowed here after move
s1 یک String است — دادهای که روی heap زندگی میکند. خط let s2 = s1؛ این مالکیت را به s2 منتقل میکند؛ s1 از آن لحظه دیگر معتبر نیست. در ++C همین کد کامپایل میشد و s1 یا دادهی خراب نشان میداد یا کرش میکرد — رفتاری که فقط با تست دقیق پیدا میشود، نه با خواندن کد. کامپایلر Rust همین را همانجا رد میکند.
مثال دوم: قرضی که هنوز باز است
rustfn main() {
let mut v = vec![1, 2, 3];
let first = &v[0];
v.push(4);
println!("first is: {}", first);
}
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
--> main.rs:4:5
|
3 | let first = &v[0];
| - immutable borrow occurs here
4 | v.push(4);
| ^^^^^^^^^ mutable borrow occurs here
این یکی ظریفتر است: v.push(4) میتواند بافر پشتِ vec را کامل جابهجا کند تا جا برای عضو جدید باز شود — و اگر این اتفاق بیفتد، first به آدرسی اشاره میکند که دیگر وجود ندارد. در بیشتر زبانها این یک باگ نادر است که فقط گاهی، وقتی بافر واقعاً جابهجا شود، خودش را نشان میدهد. Rust صبر نمیکند ببیند جابهجایی واقعاً اتفاق میافتد یا نه؛ قانون ساده است: تا وقتی یک قرض غیرقابلتغییر (&v[0]) باز است، نمیشود از خودِ v بهصورت قابلتغییر استفاده کرد.
راهحل، معمولاً یکی از این دو تاست
rustfn main() {
let mut v = vec![1, 2, 3];
let first = v[0]; // کپی مقدار، نه قرض گرفتن آدرس
v.push(4);
println!("first is: {}", first);
println!("v is now: {:?}", v);
}
first is: 1
v is now: [1, 2, 3, 4]
چون i32 تریت Copy دارد، v[0] یک عدد کپی میکند نه یک قرض — تعارضی باقی نمیماند. برای مثال اول هم دو راه هست: s1.clone() بزنی، یا از اول بهجای انتقال مالکیت، رفرنس بدهی:
rustfn greet(name: &String) {
println!("hello, {}!", name);
}
fn main() {
let s1 = String::from("hello");
greet(&s1);
println!("still have s1: {}", s1);
}
hello, hello!
still have s1: hello
با &s1، تابع greet فقط قرض میگیرد، مالکیت را نمیبرد — s1 بعد از فراخوانی هم در دسترس میماند.
اینها را کامپایلر چک میکند، نه شما
نکتهی اصلی همین است: در C یا ++C، درست بودن مدیریت حافظه به انضباط برنامهنویس و کیفیت تستها بستگی دارد. در Rust، اینها قوانین زباناند و کامپایلر (borrow checker) قبل از اینکه کد اصلاً اجرا شود، رعایتشان را تضمین میکند. هزینهاش یک منحنی یادگیری واقعی در چند هفتهی اول است؛ نتیجهاش نبودِ یک دسته کامل از باگهای حافظه که در زبانهای دیگر فقط در پروداکشن پیدا میشوند.
اگر میخواهید Rust را از صفر و با تمرین واقعی یاد بگیرید، دورهی Rust از صفر تا برنامهنویس همین قوانین مالکیت و قرضگیری را قدمبهقدم جا میاندازد.





نظرها
اولین نفری باشید که نظر میدهدسؤالتان را بپرسید یا تجربهتان را بنویسید — نویسندهی مطلب پاسخ میدهد.