Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

سرزمین راست: ماجراهای فریس خرچنگ فضایی

فصل ۴: باشگاه امانت‌دهی فریس (معرفی مالکیت با اسباب‌بازی)

📑 فهرست فصل

۴.۱. دعوا سر تراکتور قرمز (مفهوم Move)
۴.۱.۱. داستان: تراکتوری که فقط یک مالک داشت
۴.۱.۲. قانون اول: هر مقدار یک مالک دارد
۴.۱.۳. انتقال مالکیت (Move) در عمل
۴.۱.۴. چرا بعضی چیزها کپی می‌شوند؟ (انواع Copy)
۴.۱.۵. صفحه مصور: جعبه و برچسب مالک
۴.۲. کارت امانت (Borrowing & References)
۴.۲.۱. راه حل دعوا: کارت امانت به جای اسباب‌بازی
۴.۲.۲. ساختن مرجع با &
۴.۲.۳. قانون: هر تعداد کارت امانت معمولی مجاز است
۴.۲.۴. کارت امانت ویژه برای تغییر (&mut)
۴.۲.۵. قانون طلایی فریس (خلاصه‌ی قوانین امانت)
۴.۳. نگهبان باشگاه: Borrow Checker
۴.۳.۱. معرفی نگهبان
۴.۳.۲. طول عمر کارت امانت (یک اشاره برای آینده)
۴.۳.۳. تمرین: قانون‌شکنی عمدی (و دیدن خطاهای دوستانه)
۴.۴. تکه‌های پازل (Slices)
۴.۴.۱. گاهی فقط به بخشی از یک متن نیاز داریم
۴.۴.۲. ساختن slice با محدوده (Range)
۴.۴.۳. نکته‌ی مهم: slice هم یک کارت امانت است
۴.۴.۴. تمرین: پیدا کردن اولین کلمه
۴.۵. جمع‌بندی و چالش
۴.۵.۱. سه قانون اصلی مالکیت
۴.۵.۲. چالش: تابعی که مالکیت نمی‌گیرد
۴.۵.۳. حرف آخر: نگران نباش، تمرین کن!


۴.۱. دعوا سر تراکتور قرمز (مفهوم Move)

۴.۱.۱. داستان: تراکتوری که فقط یک مالک داشت

فریس یک تراکتور قرمز خیلی خوشگل داره. 🚜💨 یک روز دوستش بیل می‌آید و می‌گوید: «چه تراکتور باحالی! می‌شود من هم یک کم باهاش بازی کنم؟» فریس که خرچنگ مهربونیه، می‌گوید: «بله، بگیرش مال خودت!» و تراکتور را می‌دهد دست بیل.
بعداً که دلش برای تراکتور تنگ می‌شود و می‌خواهد دوباره باهاش بازی کند، تازه یادش می‌آید که دیگر تراکتوری ندارد! چون آن را بخشیده بود.
توی دنیای Rust هم دقیقاً همین اتفاق می‌افتد و بهش می‌گویند انتقال مالکیت (Move).

۴.۱.۲. قانون اول: هر مقدار یک مالک دارد

توی Rust هر چیزی که می‌سازی (مثلاً یک متن، یک لیست، یا یک اسباب‌بازی) فقط یک صاحب دارد. به آن صاحب می‌گوییم مالک (Owner). تا وقتی مالک هست، می‌تواند ازش استفاده کند. وقتی مالک از صحنه خارج بشود (مثلاً تابع تمام شود یا به آکولاد بسته برسد)، آن چیز خود به خود از حافظه پاک می‌شود. اینطوری حافظه‌ی کامپیوتر همیشه تمیز و مرتب می‌ماند و پر از آشغال نمی‌شود! 🧹✨
یعنی تو داری یاد می‌گیری چطور کامپیوتر حافظه را مدیریت می‌کند – یک گام بزرگ به سمت جادوگر کامپیوتر شدن! 🧙‍♂️

۴.۱.۳. انتقال مالکیت (Move) در عمل

بیا این اتفاق را توی کد Rust ببینیم:

fn main() {
    let s1 = String::from("تراکتور"); // s1 مالک است
    let s2 = s1;                      // مالکیت از s1 به s2 منتقل شد (Move)

    // println!("{}", s1);            // ❌ اگر این خط را فعال کنی، خطا می‌گیری!
    println!("{}", s2);               // ✅ این خط درست کار می‌کند
}

کامپایلر سریع می‌گوید: value moved. یعنی: «رفیق، این دیگر مال تو نیست! مالکیت رفت پیش s2

۴.۱.۴. چرا بعضی چیزها کپی می‌شوند؟ (انواع Copy)

شاید بگویی: «اما من قبلاً عددها را اینطوری جابه‌جا می‌کردم و خطا نمی‌گرفتم!»
دقیقاً درست حدس زدی. بعضی چیزها مثل اعداد ساده (i32، f64) یا کاراکترها (char) آن‌قدر سبک و کوچک اند که Rust به جای انتقال مالکیت، یک کپی ازشان می‌سازد:

#![allow(unused)]
fn main() {
let x = 5;
let y = x; // اینجا x کپی می‌شود، نه انتقال مالکیت
println!("x = {} , y = {}", x, y); // هر دو درست کار می‌کنند
}

چرا؟ چون کپی کردن یک عدد مثل کپی کردن یک عکس در مغزت است؛ سریع و بی‌هزینه است. ولی String می‌تواند خیلی بزرگ باشد (مثل یک کتاب هزار صفحه‌ای). کپی کردنش وقت و حافظه زیادی می‌برد. پس Rust ترجیح می‌دهد فقط مالکیتش را جابه‌جا کند. به چیزهایی که مثل عدد کپی می‌شوند، می‌گوییم از نوع Copy هستند.

👨‍👩‍👧 نکته برای والدین و مربیان
این فصل سخت‌ترین و منحصربه‌فردترین مفهوم Rust را معرفی می‌کند: مالکیت. هدف، تسلط کامل نیست، بلکه آشنایی با این ایده است که Rust برای هر داده یک «مسئول» مشخص می‌کند. اگر کودک همه‌ی جزئیات را نفهمید، نگران نباشید – در فصل‌های بعدی بارها با این قوانین روبرو خواهد شد. برای توضیحات عمیق‌تر، کتاب رسمی Rust فصل کاملی درباره‌ی مالکیت دارد:
doc.rust-lang.org/book/ch04-00-understanding-ownership.html

[Illustration: Educational illustration showing two labeled boxes: “s1” (empty, crossed out) and “s2” (holding a shiny red toy tractor). An arrow shows ownership moving from s1 to s2. Ferris the crab stands beside them pointing at the boxes, looking slightly surprised but happy. Style: bright children’s book illustration, clean lines, metaphorical, 16:9.]


۴.۲. کارت امانت (Borrowing & References)

۴.۲.۱. راه حل دعوا: کارت امانت به جای اسباب‌بازی

فریس نباید تراکتور را می‌بخشید. کافی بود به بیل یک کارت امانت می‌داد. با کارت امانت، بیل می‌تواند تراکتور را ببیند، حتی اگر اجازه داشته باشد سوارش بشود، ولی تراکتور همچنان در انبار فریس باقی می‌ماند.
توی Rust به این کارت امانت مرجع (Reference) می‌گوییم. عمل قرض دادن را هم Borrowing می‌نامیم.

۴.۲.۲. ساختن مرجع با &

با گذاشتن یک علامت & قبل از اسم متغیر، یک کارت امانت می‌سازی:

#![allow(unused)]
fn main() {
let s1 = String::from("تراکتور");
let s2 = &s1; // s2 یک کارت امانت به s1 است

println!("مالک: {}", s1);    // فریس هنوز تراکتورش را دارد
println!("قرض‌گیرنده: {}", s2); // بیل هم می‌تواند ببیندش
}

اینجا هیچ خطایی رخ نمی‌دهد. هر دو می‌توانند از مقدار استفاده کنند، چون فقط «نگاه» کردن، نه «صاحب شدن».

۴.۲.۳. قانون: هر تعداد کارت امانت معمولی مجاز است

می‌توانی به تعداد نامحدود کارت امانت معمولی (&) صادر کنی:

#![allow(unused)]
fn main() {
let s = String::from("سلام");
let r1 = &s;
let r2 = &s;
let r3 = &s;
println!("{} {} {}", r1, r2, r3); // همه می‌توانند نگاه کنند
}

این درست مثل وقتی است که چند تا بچه دور یک آکواریوم جمع می‌شوند و به ماهی‌ها نگاه می‌کنند. تا وقتی کسی دستش را در آب نمی‌کند (تغییر نمی‌دهد)، همه خوشحالند! 🐠

۴.۲.۴. کارت امانت ویژه برای تغییر (&mut)

اما اگر بیل بخواهد تراکتور را رنگ کند (تغییر بدهد)، دیگر کارت معمولی کافی نیست. او باید یک کارت امانت ویژه و طلایی بگیرد که روش نوشته &mut. ولی یک شرط سخت دارد:
⚠️ در هر لحظه، فقط یک نفر می‌تواند این کارت ویژه را داشته باشد.

#![allow(unused)]
fn main() {
let mut s = String::from("تراکتور"); // خود تراکتور هم باید قابل تغییر باشد
let r1 = &mut s; // بیل کارت ویژه گرفت
r1.push_str(" قرمز"); // تراکتور را رنگ کرد (یعنی متن را عوض کرد)
println!("{}", r1);
}

اگر دو نفر همزمان کارت ویژه بخواهند، نگهبان داد می‌زند:

#![allow(unused)]
fn main() {
let mut s = String::from("تراکتور");
let r1 = &mut s;
let r2 = &mut s; // ❌ خطا! دو تا کارت ویژه همزمان ممنوع
}

۴.۲.۵. قانون طلایی فریس (خلاصه‌ی قوانین امانت)

این مهم‌ترین قانون باشگاه امانت‌دهی است. حتماً جایی بنویسش:
🔹 یا می‌توانی هر تعداد کارت معمولی (&) بدهی (فقط نگاه کردن).
🔹 یا می‌توانی فقط یک کارت ویژه (&mut) بدهی (نگاه + تغییر).
❌ ترکیب این دو تا مطلقاً ممنوع است!

مثال نقض قانون:

#![allow(unused)]
fn main() {
let mut s = String::from("تراکتور");
let r1 = &s;        // کارت معمولی (فقط نگاه)
let r2 = &mut s;    // ❌ خطا! نمی‌شود همزمان هم نگاه کرد، هم تغییر داد
}

[Illustration: Cartoon scene showing a “Borrowing Club” desk. On one side, multiple kids hold normal blue cards labeled “&” looking at a toy. On the other side, one kid holds a golden card labeled “&mut” and is painting the toy. Ferris stands behind the desk holding a rulebook. Style: playful, educational, vibrant colors, clear visual metaphor, 16:9.]


۴.۳. نگهبان باشگاه: Borrow Checker

۴.۳.۱. معرفی نگهبان

در کامپایلر Rust یک موجود بامزه ولی جدی زندگی می‌کند به اسم Borrow Checker (بررسی‌کننده‌ی قرض‌ها). او دقیقاً مثل نگهبان سخت‌گیر ولی مهربون باشگاه است. وظیفه‌اش این است که قوانین بالا را چک کند. اگر ببیند کسی قانون را شکسته، برنامه را متوقف می‌کند و با پیام‌های رنگی و دقیق بهت می‌گوید مشکل از کجاست.
او دوست ماست، چون نمی‌گذارد برنامه‌مان خراب شود، اطلاعات گم شود یا دو نفر همزمان یک چیز را تغییر دهند! 🛡️

۴.۳.۲. طول عمر کارت امانت (یک اشاره برای آینده)

هر کارت امانت یک تاریخ انقضا دارد. یعنی کارت فقط تا وقتی معتبر است که خود اسباب‌بازی وجود داشته باشد. اگر اسباب‌بازی خراب شود (از حافظه پاک شود)، کارت امانت بی‌ارزش و خطرناک می‌شود. کامپایلر این تاریخ‌ها را چک می‌کند. مثال زیر خطا دارد چون کارت امانت (r) بیشتر از خود اسباب‌بازی (s) عمر می‌کند:

#![allow(unused)]
fn main() {
let r;
{
    let s = String::from("سلام");
    r = &s; // کارت امانت گرفته شد
} // s اینجا از بین می‌رود (آکولاد بسته شد)

println!("{}", r); // ❌ خطا! r دارد به یک متغیر مرده اشاره می‌کند
}

(فعلاً نگران جزئیات نباشید. در فصل‌های پیشرفته‌تر با این مفهوم بیشتر آشنا می‌شوید. فقط بدانید که نگهبان حواسش به تاریخ انقضای کارت‌ها هم هست.)

۴.۳.۳. تمرین: قانون‌شکنی عمدی (و دیدن خطاهای دوستانه)

حالا خودت دست به کار شو و چند کد بنویس که عمداً قانون طلایی را بشکنند. مثلاً:

fn main() {
    let mut text = String::from("بازی");
    let a = &text;
    let b = &text;
    let c = &mut text; // اینجا نگهبان جیغ می‌زند!
    println!("{} {} {}", a, b, c);
}

کد را اجرا کن و پیام خطای کامپایلر را با دقت بخوان. ببین چقدر دقیق بهت می‌گوید که: «نمی‌شود قرض mutable داشت چون قبلاً immutable قرض دادی.» این یعنی نگهبان دارد ازت محافظت می‌کند! 🤝

[Illustration: Ferris the crab wearing a security guard uniform, holding a flashlight and checking a list of rules. In the background, a cartoon compiler robot gives a red warning light and a green checkmark next to different code blocks. Style: friendly, technical metaphor, children’s book illustration, soft lighting, 16:9.]


۴.۴. تکه‌های پازل (Slices)

۴.۴.۱. گاهی فقط به بخشی از یک متن نیاز داریم

فرض کن فریس یک تابلوی راهنما دارد که رویش نوشته: Welcome to Crab Planet. او فقط می‌خواهد کلمه‌ی Crab را برجسته کند. آیا باید کل تابلو را کپی کند؟ نه! کافی است یک ذره‌بین بردارد و فقط به آن بخش اشاره کند. توی Rust به این ذره‌بین Slice (برش) می‌گوییم.

۴.۴.۲. ساختن slice با محدوده (Range)

برای ساختن یک برش از یک متن، از علامت [start..end] استفاده می‌کنیم:

#![allow(unused)]
fn main() {
let s = String::from("Ferris the crab");
let ferris = &s[0..6];   // "Ferris" (از اندیس ۰ تا ۵)
let the_crab = &s[7..];  // "the crab" (از اندیس ۷ تا آخر)
let all = &s[..];        // "Ferris the crab" (همه‌ی متن)
}

📌 نکته‌ی مهم برای متن فارسی: در Rust اندیس‌های [..] بر اساس بایت هستند، نه کاراکتر. چون حروف فارسی و ایموجی‌ها چند بایت جا می‌گیرند، استفاده از [..] روی متن فارسی ممکن است خطا بدهد. فعلاً برای ساده‌تر شدن، با متن انگلیسی تمرین می‌کنیم.

۴.۴.۳. نکته‌ی مهم: slice هم یک کارت امانت است

برش (Slice) در اصل یک نوع مرجع (&) است. پس همان قوانین نگهبان روی آن هم اجرا می‌شود. تا وقتی یک برش از متن داری، نمی‌توانی متن اصلی را تغییر دهی:

#![allow(unused)]
fn main() {
let mut s = String::from("hello world");
let word = &s[0..5]; // کارت امانت به "hello"
s.clear();           // ❌ خطا! نمی‌شود متن را پاک کرد چون word هنوز دارد نگاه می‌کند
}

۴.۴.۴. تمرین: پیدا کردن اولین کلمه

تابعی بنویس به اسم first_word که یک &str بگیرد و اولین کلمه‌ی آن (تا قبل از فاصله) را برگرداند. اگر فاصله‌ای نبود، کل متن را برگرداند.

💡 راهنمایی: می‌توانی از متد find(' ') استفاده کنی که جایگاه بایت فاصله را برمی‌گرداند. این متد یک Option برمی‌گرداند: اگر فاصله پیدا شد، Some(موقعیت) و اگر پیدا نشد، None. (دقیقاً شبیه Result در فصل ۲، فقط به جای Ok/Err از Some/None استفاده می‌کند.)

fn first_word(s: &str) -> &str {
    match s.find(' ') {
        Some(pos) => &s[..pos], // اگر فاصله پیدا شد، تا آنجا برش بزن
        None => s,              // اگر پیدا نشد، کل متن را برگردان
    }
}

fn main() {
    let sentence = String::from("Ferris the crab");
    let word = first_word(&sentence);
    println!("اولین کلمه: {}", word); // خروجی: Ferris
}

این کد هم امن است، هم سریع، و دقیقاً همان کاری است که یک برنامه‌نویس حرفه‌ای Rust انجام می‌دهد! 🛠️

[Illustration: Close-up of a cartoon magnifying glass hovering over a long paper strip labeled “Ferris the crab”. The glass highlights only the word “Ferris”. A pair of scissors (representing slicing) rests nearby. Ferris holds the magnifying glass proudly. Style: clean, educational vector, bright colors, metaphorical, 16:9.]


۴.۵. جمع‌بندی و چالش

۴.۵.۱. سه قانون اصلی مالکیت

برای اینکه حواست جمع باشد، این سه قانون را روی یک کاغذ بنویس و بچسبان جلوی چشمات: ۱. هر مقداری در Rust دقیقاً یک مالک دارد.
۲. می‌توانی هر تعداد کارت امانت معمولی (&) بدهی، یا فقط یک کارت ویژه (&mut). ترکیبشان ممنوع است.
۳. وقتی مالک از اتاق خارج شود (scope تمام شود)، اسباب‌بازی هم خودکار جمع می‌شود.

۴.۵.۲. چالش: تابعی که مالکیت نمی‌گیرد

یک تابع به اسم calculate_length بنویس که یک &String بگیرد و طول آن را برگرداند (بدون اینکه مالکیت رشته را تصاحب کند). در تابع main یک String بساز، طولش را با این تابع حساب کن و بعد از آن دوباره از همان String استفاده کن (مثلاً چاپش کن).

💡 پاسخ نمونه:

fn calculate_length(s: &String) -> usize {
    s.len() // .len() طول رشته را برمی‌گرداند
}

fn main() {
    let my_string = String::from("Ferris the crab");
    let len = calculate_length(&my_string);
    println!("طول '{}' برابر است با {} کاراکتر", my_string, len);
    // my_string هنوز زنده است چون مالکیتش را ندادیم!
}

۴.۵.۳. حرف آخر: نگران نباش، تمرین کن!

🧠 گاهی بعضی چیزها سخت است، و این اشکالی ندارد!
ممکن است این فصل یکم برات سخت و عجیب بوده باشد. کاملاً طبیعی است! مالکیت (Ownership) معروف‌ترین و در عین حال چالش‌برانگیزترین مفهوم Rust است. حتی برنامه‌نویس‌های باتجربه هم گاهی با Borrow Checker کلنجار می‌روند. انتظار نداریم بعد از یک بار خواندن به استاد مالکیت تبدیل شوی.

اما خبر خوب این است که هر چی بیشتر کد بزنی، این قوانین بیشتر در ذهنت جا می‌افتد. مثل دوچرخه‌سواری می‌ماند؛ اولش سخت است، اما بعداً دیگر لازم نیست به تعادل فکر کنی. 🚴‍♂️💨

در فصل بعد، به سراغ یک ابزار خیلی جذاب می‌رویم: Struct یا همان «کارت شناسایی برای هیولاهای فضایی»! 🦑✨

[Illustration: Ferris the crab sitting at a cozy desk, writing the “3 Ownership Rules” on a glowing parchment. Around him float small icons: a locked box (ownership), a blue card (&), a golden card (&mut), and a magnifying glass (slice). Warm, encouraging lighting. Style: children’s book illustration, whimsical, high detail, 16:9.]