آموزش زبان Rust: اشارهگر Box چیست؟ به زبان بسیار ساده
به نام دانای برحق
این بار به زبان بسیار ساده میخوایم در مورد اشارهگر Box<T> در rust صحبت کنیم، اگر داکیومنت زبان rust رو بخونی متوجه این میشی:
اشارهگر Box<T>
فقط وقتی میخواهی چیزی را روی heap بگذاری و مالکیت تکی داشته باشی.
هیچ اشتراکی نمیدهد.
با خودت ممکنه اینو بگی:
چرا باید داده رو تو هیپ بذارم مگه مریض هستم؟
چی میشه اسلحه بالا سرم میذارن که مجبور شم از Box زوری استفاده کنم 😅؟؟
خب حالا ببینیم کجای کار میلنگه که rust اون کلاش خوشگلشو میذاره بالاسر برنامهنویس ( یا بهتر بگیم گروگان دچار سندرم استکهلم😁) میگه نه داداش من برنامهات رو کامپایل نمیکنم مگر این که به حرفم گوش بدی!
نکتهی اصلی اینه که Box<T> را معمولاً برای «heap بودن» انتخاب نمیکنیم. برای حل یک مشکل دیگر انتخابش میکنیم و heap شدن، نتیجهی اون انتخاب اس.
پس کی واقعاً مجبور میشیم در rust از Box استفاده کنیم؟
مثلاً فرض کن یک struct داری که اندازهاش به شکل مستقیم قابل محاسبه نیست:
struct Person {
name: String,
friend: Option<Person>,
}
مشکل چیه اینجا؟
Rust میپرسه:
«داداش اندازهی
Personچقدره؟»
برای اینکه Person را بسازه بدبخت باید بدونه چقدر جا بگیره!
ولی داخلش یک Person دیگه داری، که داخلش یه Person دیگه دارد، که داخلش…
پس اندازهاش بینهایت میشه. کامپایلر میگه:
نه داداش اومدی و نسازی. من چنین خبطی نمیکنم که این داده رو تو stack جا بدم. اصلاً نمیدونم چند بایته!!!
اینجا Box میآید وسط:
struct Person {
name: String,
friend: Option<Box<Person>>,
}
حالا Person اندازهی مشخصی دارد، چون فقط اشاره گرش ذخیره میشه و خود Box<Person> اندازهی مشخصی دارد؛ عملاً یک pointerه. تصویر گویا هستش چی هستش:
وضعیت داده های بزرگ در heap و کاربرد Box
فرض کن برنامهامون یه فایل خیلی بزرگ رو در حافظه نگه میداره:
let data = [0u8; 10_000_000];
ممکنه بگی: «خب بریز روی stack دیگه! 😎»
Rust میگه:
«بیلااااخ داداش، ۱۰ مگابایت روی stack؟ چرا! مگه با من پدرکشتگی داری؟»
میتونی بگی:
let data = Box::new([0u8; 10_000_000]);
حالا خود متغیر data کوچیکه و دادهی بزرگ روی heap قرار دارد.
اما نکته مهم: این مثال الزاماً به این معنی نیست که Box بهترین راه برای هر دادهی بزرگیه. مثلاً Vec<T> در بسیاری از این سناریوها انتخاب طبیعیتریه.
پس Box چه مشکلی رو حل میکنه؟
سه کاربرد مهم را رو تو ذهن داشته باشیم:
۱. دادهای داریم که اندازهاش در compile time مشخص نیست
مثل:
Box<dyn Animal>
چون انواع مختلف Animal میتونن اندازههای متفاوتی داشته باشن.
۲. می خوای یک مقدار بزرگ رو غیرمستقیم نگه داری
بهجای اینکه خود دادهی عظیم روی stack باشد:
Box<BigThing>
۳. ساختار recursive داری مثل چیزی که اول گتیم
مثال دیگه:
struct Node {
value: i32,
next: Option<Box<Node>>,
}
نه به خاطر اینکه Rust عاشق linked list اس 😂 بلکه چون Box به کامپایلر میگه: «لازم نیست کل Node داخل خودش جا بشه؛ فقط یه pointer به Node بعدی داشته باش.»
و یک سوءتفاهم خیلی مهم
این جمله:
Box<T>= «چیزی را روی heap بگذار»
کمی گمراهکننده اس.
بهتر اس در ذهنمون اینطور ذخیرهاش کنیم:
Box<T>یعنی مالکیت یکتای یکTکه بهصورت غیرمستقیم در اختیارم اس.
و اینکه T روی heap قرار میگیرد، بخشی از نحوهی پیادهسازی Box اس.
یادمون باشه Box اشتراک مالکیت نمیده هرگز.
برای اشتراک مالکیت معمولاً سراغ چیزهایی مثل Rc<T> یا Arc<T> میریم. بنابراین جواب سؤال «چرا باید داده رو ببرم heap؟ مگه مریضم؟» اینه: اغلب اوقات اصلاً نباید.😂
اگر کارت با stack راحت انجام میشه:
let x = 42;
همین رو انجام بده. لازم نیست برای اینکه برنامهات «حرفهایتر» به نظر برسد، Box بچپونی وسطش.😋
Box بیشتر زمانی وارد میشه که بدون indirection/heap اصلاً نمیتونیم چیزی را با type system و قوانین Rust بیان کنی یا مشخصاً به چنین allocationای نیاز داری.
و این دقیقاً جاییه که Rust میگه:
«من نگفتم heap خوبه؛ گفتم این مشکل بدون یه pointer حل نمیشه. حالا
Boxرو بردار و برو 😂»
اول از همه کوچک ترین عضو مجموعه 4xmen بنده حقیر هستم.
در ثانی یک توسعه دهنده هستم زمینه های تخصصی کارم :
Delphi, Rust ,PHP, JavaScript, Unreal engine, asm, Dart
و علاقه مند به لینوکس، RCE ، کریپتوگرافی هستم.
تا الان حضرت حق اینا رو به ما داده و هر وقت که بخواد ازمون میگیره دست خودشه.
یا حق، با حق، تا حق

شاید امروز جواب خیلی از سؤالها را بتوان از AI گرفت، اما تولید مطالب تخصصی، تجربهمحور و عمیق هنوز به انگیزه نیاز دارد. این نظرات و بازخوردهای شماست که به ما نشان میدهد ادامه دادن این مسیر ارزشمند است.
دیدگاه شما چیست؟