مقدمه
به فصل «قابلیتهای پیشرفتهتر Rust» خوش آمدید. تاکنون تمام کدهایی که نوشتهایم، توسط تضمینهای
ایمنی حافظه کامپایلر Rust در زمان کامپایل بررسی شدهاند. اما Rust یک زبان دیگر در دل خود پنهان
کرده که این تضمینها را اعمال نمیکند: «راست ناامن» یا Unsafe Rust. این قابلیت به ما
اجازه میدهد تا برخی از محدودیتهای کامپایلر را دور بزنیم و کنترل سطح پایینتری بر روی سیستم
داشته باشیم.
استفاده از کد ناامن به معنی غیرفعال کردن borrow checker یا دیگر مکانیزمهای ایمنی Rust نیست.
بلکه به شما اجازه میدهد تا به پنج قابلیت اضافی دسترسی پیدا کنید که کامپایلر نمیتواند ایمنی
آنها را تضمین کند. این قابلیتها به «ابرقدرتهای ناامن» (unsafe superpowers) معروفند و باید با
احتیاط فراوان استفاده شوند.
ابرقدرتهای ناامن
برای استفاده از این قابلیتها، باید کد خود را در یک بلوک unsafe قرار دهیم. این بلوک به
کامپایلر میگوید: "به من اعتماد کن، من میدانم چه کار میکنم و مسئولیت ایمنی این بخش از کد را بر
عهده میگیرم." این پنج ابرقدرت عبارتند از:
- Dereference کردن یک اشارهگر خام (raw pointer)
- فراخوانی یک تابع یا متد ناامن
- دسترسی یا تغییر یک متغیر استاتیک تغییرپذیر
- پیادهسازی یک trait ناامن
- دسترسی به فیلدهای یک union
در این درس، ما بر روی اولین و رایجترین مورد، یعنی کار با اشارهگرهای خام، تمرکز خواهیم کرد.
کار با اشارهگرهای خام (Raw Pointers)
اشارهگرهای خام، که به دو صورت *const T (تغییرناپذیر) و *mut T (تغییرپذیر) وجود دارند، شبیه
به رفرنسها هستند اما با این تفاوت که قوانین وامگیری روی آنها اعمال نمیشود.
- آنها مجازند که چندین رفرنس تغییرپذیر و تغییرناپذیر به صورت همزمان به یک مکان داشته باشند.
- تضمینی برای معتبر بودن آدرسی که به آن اشاره میکنند، وجود ندارد.
- آنها مجازند که null باشند.
- آنها هیچ مکانیزم ردیابی خودکاری (مانند شمارش ارجاع) را پیادهسازی نمیکنند.
شما میتوانید یک اشارهگر خام را در کد ایمن ایجاد کنید، اما برای dereference کردن آن (یعنی
دسترسی به دادهای که به آن اشاره میکند)، باید حتماً در یک بلوک unsafe باشید.
src/main.rs
fn main() {
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
unsafe {
println!("r1 is: {}", *r1);
println!("r2 is: {}", *r2);
}
}
در این کد، ما از رفرنسهای عادی، دو اشارهگر خام میسازیم. این کار ایمن است. اما برای خواندن
مقدار آنها با عملگر `*`، کد را در یک بلوک unsafe قرار دادهایم. با این کار، ما به
کامپایلر میگوییم که تضمین میکنیم این اشارهگرها در این لحظه معتبر هستند.
فراخوانی توابع ناامن
دومین ابرقدرت ناامن، فراخوانی توابع و متدهایی است که با کلمه کلیدی unsafe علامتگذاری
شدهاند. این توابع پیششرطهایی دارند که کامپایلر نمیتواند آنها را بررسی کند و این وظیفه
برنامهنویس است که قبل از فراخوانی، از برقراری آن پیششرطها اطمینان حاصل کند.
یک مثال رایج، فراخوانی توابع از زبانهای دیگر (مانند C) از طریق Foreign Function Interface
(FFI) است.
src/main.rs
extern "C" {
fn abs(input: i32) -> i32;
}
fn main() {
unsafe {
println!("Absolute value of -3 according to C is {}", abs(-3));
}
}
در اینجا، ما تابع abs را از کتابخانه استاندارد زبان C فراخوانی میکنیم. از آنجایی که کامپایلر
Rust نمیتواند ایمنی کدهای خارجی را تضمین کند، ما باید فراخوانی آن را در یک بلوک unsafe
قرار دهیم.
در این درس با مفهوم کد ناامن در Rust و ابرقدرتهایی که در اختیار ما قرار میدهد، آشنا شدیم.
دیدیم که چگونه با استفاده از بلوک unsafe میتوانیم کنترل سطح پایینتری بر روی سیستم داشته
باشیم، اما در عین حال مسئولیت تضمین ایمنی آن کد را نیز بر عهده میگیریم. در درس بعدی، به بررسی
«چند Trait پیشرفته» خواهیم پرداخت که به ما اجازه میدهند تا قابلیتهای بسیار قدرتمندی را در
نوعهای خود پیادهسازی کنیم.