سرزمین راست: ماجراهای فریس خرچنگ فضایی
فصل ۱۹: موتور سفینه را باز کنیم! (Unsafe Rust و ماکروها)
📑 فهرست فصل
۱۹.۱. هشدار جدی: اینجا اتاق موتور است (unsafe)
۱۹.۱.۱. داستان: موتور داغ سفینه
۱۹.۱.۲. unsafe چیست؟
۱۹.۱.۳. کارهایی که در unsafe میتوان کرد
۱۹.۱.۴. مثال: اشارهگر خام (Raw Pointer)
۱۹.۱.۵. فراخوانی تابع unsafe
۱۹.۱.۶. تمرین: اصلاح یک اشارهگر خام
۱۹.۲. ساخت جادوی اختصاصی خودت (Macros)
۱۹.۲.۱. داستان: کلمهی جادویی فریس
۱۹.۲.۲. ماکرو چیست و چه فرقی با تابع دارد؟
۱۹.۲.۳. سادهترین ماکرو با macro_rules!
۱۹.۲.۴. ماکرو با پارامتر (ident)
۱۹.۲.۵. ماکرو با تعداد متغیر آرگومان
۱۹.۲.۶. ماکروهای پرکاربرد در Rust
۱۹.۲.۷. تمرین: ماکروی easy_vec!
۱۹.۳. پروژه: ساخت ماکروی repeat!
۱۹.۳.۱. هدف
۱۹.۳.۲. پیادهسازی repeat!
۱۹.۳.۳. تست ماکرو
۱۹.۴. جمعبندی و چالش
۱۹.۴.۱. مرور مفاهیم
۱۹.۴.۲. چالش: ماکروی create_function!
۱۹.۱. هشدار جدی: اینجا اتاق موتور است (unsafe)
۱۹.۱.۱. داستان: موتور داغ سفینه
در اعماق سفینهی فریس، یک در فلزی سنگین وجود دارد که روی آن با رنگ قرمز نوشته شده: ⛔ خطر! فقط برای مهندسهای ارشد. پشت این در، موتور اصلی سفینه کار میکند. دما آنجا بسیار بالاست و اگر کسی بدون آموزش وارد شود، ممکن است همهچیز منفجر شود! 💥
در دنیای Rust، بیشتر وقتها ما در بخشهای امن (Safe Rust) کار میکنیم، جایی که کامپایلر مثل یک نگهبان مهربان، همهی قوانین را چک میکند تا ما اشتباه نکنیم. ولی گاهی برای کارهای خیلی خاص (مثل صحبت مستقیم با سختافزار یا استفاده از کتابخانههای قدیمی زبان C)، مجبوریم وارد اتاق unsafe شویم.
یادگیری unsafe یعنی تو میفهمی قدرت واقعی Rust کجاست – اما مثل یک جادوگر دانا، فقط در مواقع ضروری از آن استفاده میکنی. 🧙♂️
👨👩👧 نکته برای والدین و مربیان
unsafeیکی از پیشرفتهترین موضوعات Rust است و به ندرت در برنامههای معمولی استفاده میشود. هدف این فصل آشنایی با وجود این قابلیت است، نه تشویق به استفاده از آن. کتاب رسمی Rust فصل کاملی دربارهیunsafeدارد:
doc.rust-lang.org/book/ch19-01-unsafe-rust.html
۱۹.۱.۲. unsafe چیست؟
unsafe یک کلیدواژه است که به کامپایلر میگوید: «رفیق، اینجا من خودم حواسم هست و مسئولیت رعایت ایمنی حافظه را قبول میکنم. لطفاً بررسیهایت را غیرفعال کن تا بتوانم کارم را انجام دهم.»
⚠️ نکتهی طلایی: unsafe به معنی «ناامن» نیست! یعنی کامپایلر قوانین عادی را اجرا نمیکند، ولی تو باید مثل یک مهندس حرفهای، خودت مراقب باشی که دادهها را خراب نکنی. فقط وقتی از آن استفاده کن که واقعاً چارهی دیگری نباشد!
#![allow(unused)]
fn main() {
unsafe {
// اینجا میتوانیم کارهای سطح پایین انجام دهیم
}
}
۱۹.۱.۳. کارهایی که در unsafe میتوان کرد
داخل بلوک unsafe، پنج کار مجاز میشود که در کد معمولی ممنوع است:
🔹 دنبال کردن اشارهگرهای خام (*const T و *mut T)
🔹 صدا زدن توابع unsafe (معمولاً تابعهای زبان C)
🔹 تغییر متغیرهای سراسری قابل تغییر (static mut)
🔹 پیادهسازی Traitهای unsafe
🔹 دسترسی به فیلدهای union (اجتماع)
۱۹.۱.۴. مثال: اشارهگر خام (Raw Pointer)
در Rust معمولی، ما از & و &mut استفاده میکنیم که همیشه امن و معتبرند. اما اشارهگرهای خام (*const T و *mut T) میتوانند به هر آدرسی اشاره کنند (حتی به آدرس صفر یا null). کامپایلر نمیتواند چک کند این آدرس معتبر است یا نه، پس فقط در unsafe اجازه داریم محتوای داخل آن را بخوانیم یا تغییر دهیم:
fn main() {
let mut num = 5;
// ساختن اشارهگر خام (اینجا unsafe نیست، فقط داریم آدرس را میگیریم)
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
// خواندن یا نوشتن در اشارهگر خام حتماً باید unsafe باشد
unsafe {
println!("مقدار r1: {}", *r1); // 5
*r2 = 10; // تغییر مقدار از طریق اشارهگر
println!("مقدار جدید r2: {}", *r2); // 10
}
}
۱۹.۱.۵. فراخوانی تابع unsafe
بعضی تابعها در Rust برچسب unsafe دارند. برای صدا زدنشان حتماً باید در بلوک unsafe باشی. یکی از کاربردهای رایج آن، ارتباط با کدهای زبان C است (به آن میگویند FFI):
extern "C" {
fn abs(input: i32) -> i32; // تابع قدر مطلق از کتابخانه C
}
fn main() {
let x = -5;
unsafe {
println!("قدر مطلق {} برابر است با {}", x, abs(x)); // 5
}
}
۱۹.۱.۶. تمرین: اصلاح یک اشارهگر خام
یک متغیر mut از نوع i32 با مقدار 42 بساز. یک اشارهگر خام *mut i32 به آن بساز. در یک بلوک unsafe، مقدارش را به 100 تغییر بده و چاپش کن.
💡 پاسخ نمونه:
fn main() {
let mut value = 42;
let raw_ptr = &mut value as *mut i32;
unsafe {
*raw_ptr = 100;
println!("مقدار جدید: {}", *raw_ptr);
}
}
![[Illustration: Cartoon spaceship engine room with glowing warning signs labeled “unsafe”. Ferris wears a protective engineer suit and helmet, carefully turning a glowing valve labeled “*mut T”. Steam and soft blue energy fill the room. Style: dramatic but child-friendly, high contrast, vibrant children’s book illustration, 16:9.]](assets/images/19.1.png)
۱۹.۲. ساخت جادوی اختصاصی خودت (Macros)
۱۹.۲.۱. داستان: کلمهی جادویی فریس
فریس خسته شده از اینکه هر روز مجبور است یک دستور طولانی را برای کارهای تکراری بنویسد. مثلاً هر بار که میخواهد یک وکتور با چند عدد بسازد، باید Vec::new() بنویسد و چند بار push کند. یک روز یک کتاب جادویی پیدا میکند که به او یاد میدهد چطور طلسمهای اختصاصی خودش را بسازد. در Rust به این طلسمها ماکرو (Macro) میگوییم! ✨📖
۱۹.۲.۲. ماکرو چیست و چه فرقی با تابع دارد؟
| ویژگی | تابع (Function) | ماکرو (Macro) |
|---|---|---|
| زمان اجرا | هنگام اجرای برنامه (runtime) | هنگام کامپایل (compile-time) |
| کار اصلی | انجام عملیات و محاسبه | تولید کد Rust به صورت خودکار |
| تعداد ورودی | ثابت و مشخص | میتواند متغیر و دلخواه باشد |
| علامت | اسم خالی | با علامت ! تمام میشود (println!) |
ماکروها مثل یک کارخانهی کپیپیست هوشمند هستند که قبل از ساخته شدن برنامه، کد را برایت مینویسند!
۱۹.۲.۳. سادهترین ماکرو با macro_rules!
برای ساخت ماکرو از macro_rules! استفاده میکنیم:
macro_rules! say_hello {
() => {
println!("سلام از ماکروی جادویی! 🦀");
};
}
fn main() {
say_hello!(); // هنگام کامپایل تبدیل میشود به println!
}
() یعنی «اگر ماکرو را بدون آرگومان صدا زدی، کد سمت راست را جایگزین کن».
۱۹.۲.۴. ماکرو با پارامتر (ident)
میتوانیم به ماکرو ورودی بدهیم. مثلاً یک اسم تابع بگیرد و خودش تابع را بسازد:
macro_rules! create_function {
($name:ident) => {
fn $name() {
println!("تابع {} صدا زده شد! ✨", stringify!($name));
}
};
}
create_function!(foo);
create_function!(bar);
fn main() {
foo(); // چاپ میکند: تابع foo صدا زده شد!
bar(); // چاپ میکند: تابع bar صدا زده شد!
}
$name:ident یعنی «یک شناسهی معتبر (مثل اسم متغیر یا تابع) بگیر». stringify! هم اسم را به رشته تبدیل میکند.
۱۹.۲.۵. ماکرو با تعداد متغیر آرگومان
جادوی اصلی ماکروها اینجاست: میتوانند هر تعدادی ورودی بگیرند! با $($x:expr),* میگوییم «صفر یا چند عبارت که با ویرگول جدا شدهاند»:
macro_rules! sum {
($($x:expr),*) => {
{
let mut total = 0;
$(total += $x;)* // این خط به ازای هر ورودی تکرار میشود
total
}
};
}
fn main() {
let s = sum!(1, 2, 3, 4);
println!("حاصل جمع: {}", s); // 10
}
$(...)* یعنی «الگوی داخل پرانتز را به تعداد ورودیها تکرار کن».
۱۹.۲.۶. ماکروهای پرکاربرد در Rust
| ماکرو | کاربرد |
|---|---|
println!(...) | چاپ در ترمینال |
vec![...] | ساخت سریع وکتور |
format!(...) | ساخت رشتهی فرمتشده |
assert!(...) | بررسی شرط در تستها |
dbg!(...) | چاپ سریع مقدار برای دیباگ |
۱۹.۲.۷. تمرین: ماکروی easy_vec!
ماکرویی بساز که دقیقاً مثل vec! کار کند: یک لیست از مقادیر بگیرد و یک Vec برگرداند.
💡 پاسخ:
macro_rules! easy_vec {
($($x:expr),* $(,)?) => {
{
let mut v = Vec::new();
$(v.push($x);)*
v
}
};
}
fn main() {
let v = easy_vec![10, 20, 30];
println!("{:?}", v); // [10, 20, 30]
}
💡 $(,)? یعنی «اگر ته لیست ویرگول اضافه گذاشتی، اشکالی ندارد».
![[Illustration: A cartoon wizard crab (Ferris) holding a glowing spellbook labeled “macro_rules!”. Floating magical symbols like “$x:expr” and “=>” transform into actual Rust code blocks. Background: cozy library with starry windows. Style: whimsical, educational children’s book illustration, soft lighting, 16:9.]](assets/images/19.2.png)
۱۹.۳. پروژه: ساخت ماکروی repeat!
۱۹.۳.۱. هدف
ماکرویی میسازیم که یک دستور را چندین بار تکرار کند. مثلاً:
#![allow(unused)]
fn main() {
repeat!(println!("سلام! 🦀"), 3);
}
و خروجی بگیریم:
سلام! 🦀
سلام! 🦀
سلام! 🦀
۱۹.۳.۲. پیادهسازی repeat!
برای اینکه ماکرو بتواند دستورات کامل (مثل let x = 5;) را هم تکرار کند، از stmt (statement) به جای expr استفاده میکنیم:
#![allow(unused)]
fn main() {
macro_rules! repeat {
($cmd:stmt, $count:expr) => {
for _ in 0..$count {
$cmd;
}
};
}
}
خیلی ساده است! $cmd همان دستور است و $count تعداد تکرار. ماکرو آن را تبدیل به یک حلقهی for میکند.
۱۹.۳.۳. تست ماکرو
fn main() {
repeat!(println!("فریس باحال است!"), 3);
repeat!(let x = 5; println!("x = {}", x), 2);
}
خروجی دقیقاً همان چیزی است که میخواستیم. میبینی چطور با چند خط کد، یک ابزار سفارشی ساختیم؟ 🛠️✨
۱۹.۴. جمعبندی و چالش
۱۹.۴.۱. مرور مفاهیم
✅ unsafe: بخشی از Rust که مسئولیت ایمنی حافظه به عهدهی برنامهنویس است. فقط برای کارهای سطح پایین و ضروری.
✅ اشارهگرهای خام (*const T, *mut T): بدون بررسی کامپایلر، حتماً در unsafe استفاده شوند.
✅ ماکرو (macro_rules!): تولیدکنندهی کد در زمان کامپایل. با ! تمام میشود.
✅ الگوهای ماکرو: $name:ident (اسم)، $($x:expr),* (لیست عبارات).
✅ $(...)*: تکرار کد به تعداد ورودیها.
✅ اینجا به عمیقترین لایههای Rust سفر کردی – از موتور unsafe تا جادوی ماکروها. یک جادوگر کامپیوتر واقعی حالا میدانی چه ابزارهایی در اختیار داری! 🧙
🧠 گاهی بعضی چیزها سخت است، و این اشکالی ندارد!
unsafeو ماکروها از پیشرفتهترین مباحث Rust هستند. حتی برنامهنویسان حرفهای هم به ندرت ازunsafeاستفاده میکنند و ماکروهای پیچیده را با احتیاط مینویسند. اگر هنوز احساس میکنی همه چیز را نمیفهمی، نگران نباش – این فصل برای آشنایی است، نه تسلط کامل. مهم این است که بدانی چطور و کجا میتوانی از این ابزارها استفاده کنی.
۱۹.۴.۲. چالش: ماکروی create_function!
ماکرویی بساز که یک اسم تابع، یک عدد شروع و یک عدد پایان بگیرد. تابعی تولید کند که اعداد از شروع تا پایان را چاپ کند. از stringify! برای نمایش اسم تابع استفاده کن.
💡 پاسخ نمونه:
macro_rules! create_function {
($name:ident, $start:expr, $end:expr) => {
fn $name() {
println!("تابع {} اجرا شد: 👇", stringify!($name));
for i in $start..=$end {
println!(" عدد: {}", i);
}
}
};
}
create_function!(count_to_five, 1, 5);
fn main() {
count_to_five();
}
🔚 پایان فصل ۱۹
حالا تو هم بلدی چطور در مواقع ضروری سری به اتاق موتور بزنی و هم میتوانی جادوهای اختصاصی خودت را با ماکروها بسازی. در فصل بیستم، آخرین ماجراجویی ما، یک شبکهی کوچک بین دوستان فریس راه میاندازیم تا پیامهای مخفیانه بفرستند و یک چت روم فضایی بسازیم! آمادهای برای پایان این سفر فضایی؟ 🌌📡🦀
![[Illustration: Ferris wearing a graduation cap and safety goggles, holding a glowing “Chapter 19 Master” badge. Floating around him are an unsafe engine core, macro spell symbols, and a repeating loop arrow. Encouraging, vibrant children’s book illustration, celebratory mood, 16:9.]](assets/images/19.3.png)