آموزش زبان Rust: اشاره‌گر Box چیست؟ به زبان بسیار ساده

22 مهر 1405 rust راست

به نام دانای برحق

این بار به زبان بسیار ساده می‌خوایم در مورد اشاره‌گر 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 دیگه دارد، که داخلش…

در rust چرا به box نیاز داریمپس اندازه‌اش بی‌نهایت میشه. کامپایلر میگه:

نه داداش اومدی و نسازی. من چنین خبطی نمی‌کنم که این داده  رو تو stack جا بدم. اصلاً نمی‌دونم چند بایته!!!

اینجا Box می‌آید وسط:

struct Person {
name: String,
friend: Option<Box<Person>>,
}

حالا Person اندازه‌ی مشخصی دارد، چون فقط اشاره گرش ذخیره میشه و خود Box<Person> اندازه‌ی مشخصی دارد؛ عملاً یک pointerه. تصویر گویا هستش چی هستش:

چطور box در زبان rust به داد ما میرسه

وضعیت داده های بزرگ در 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 رو بردار و برو 😂»

💬

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

دیدگاه شما چیست؟

نظرات (0)


ارسال نظر

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

عضویت در شبکه‌های اجتماعی

جدیدترین مقالات و نکات برنامه‌نویسی را سریع تر از دیگران مطلع شوید.