فهرست مطالب
روی یک برنامهی 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 همین کد کامپایل میشود و کار هم میکند؛ کامپایلر بیسروصدا یک کپی کامل میسازد و هزینهاش را نشان نمیدهد. کامپایلر 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 از صفر تا برنامهنویس همین قوانین مالکیت و قرضگیری را قدمبهقدم جا میاندازد.




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