چرا Rust به‌جای segfault، خطای کامپایل می‌دهد
آموزش

چرا Rust به‌جای segfault، خطای کامپایل می‌دهد

در C یا ++C خطای حافظه معمولاً ماه‌ها بعد، در پروداکشن، خودش را نشان می‌دهد. در Rust کامپایلر همان اشتباه را همان لحظه‌ی کامپایل می‌گیرد

تحریریه‌ی دینا کد
نویسنده
۵ دقیقه مطالعه۳ بازدید
اشتراک گذاری۳
فهرست مطالب
  1. ۱مقداری که جابه‌جا می‌شود
  2. ۲قرضی که هنوز باز است
  3. ۳راه‌حل، معمولاً یکی از این دو تاست
  4. ۴این‌ها را کامپایلر چک می‌کند، نه شما

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

برچسب‌ها:#Rust#حافظه#مبتدی
تحریریه‌ی دینا کد
نویسنده

نظرها

اولین نفری باشید که نظر می‌دهد
برای ثبت نظر وارد شوید

سؤالتان را بپرسید یا تجربه‌تان را بنویسید تا نویسنده‌ی مطلب پاسخ بدهد.

مطالب مرتبط

از همین دسته
مدیریت خطا با try و except در پایتون
آموزش

مدیریت خطا با try و except در پایتون

راهنمای گام‌به‌گام مهار خطاهای زمان اجرا در پایتون با بلوک‌های try و except همراه با بررسی خطاهای رایج و ساختار اصولی.

تحریریه‌ی دینا کد۵ دقیقه
مدیریت خطا در Rust با Result و Option
آموزش

مدیریت خطا در Rust با Result و Option

زبان Rust به جای ساختارهای سنتی استثنا از نوع‌های Result و Option برای مدیریت شفاف و پیش‌بینی‌پذیر خطاها در زمان کامپایل استفاده می‌کند.

تحریریه‌ی دینا کد۶ دقیقه
تفاوت واحدهای px و em و rem در CSS و کاربرد هرکدام
آموزش

تفاوت واحدهای px و em و rem در CSS و کاربرد هرکدام

واحدهای px، em و rem هرکدام برای بخش مشخصی از صفحه‌آرایی وب طراحی شده‌اند و انتخاب نادرست آن‌ها مقیاس‌پذیری و دسترسی‌پذیری سایت را مختل می‌کند.

تحریریه‌ی دینا کد۶ دقیقه