یکی از چیزایی که زبان برنامه نویسی rust از سایر زبانها متمایز میکنه ویژگی های خاص این زبان برنامهنویسی به ویژه بحث مالکیت و سختگیری های مدیریت حافظه در rust ممکنه آزاردهنده باشه، با توجه به مطالعات و تجربیاتم در زمینه rust به این مطلب رو مینویسم که تا ببینیم چطور میشه از اینها استفاده کرد و انتخاب های پیچیده رو در قالب یک طرح مفهومی پیاده کنیم.
قانون طلایی هر چیزی رو با چاشنی نظر بیان میکنیم. سعی میکنم مطلب رو از ساده به پیچیده تر طرح کنم:
انتخاب بین String, &str در زبان rust
این انتخاب معمولا در پارامتر های ورودی توابع و ساختار struc استفاده مطرح میشه، قانون طلایی استفاده چیه؟
اگر سوال بالا رو نتونستم جواب بدم چه کار کنم؟ &str بذار اگر گیر کردی پس مجبوری String بذاری.
یه سوال مهم دیگه: اگر همیشه string استفاده کنم چی میشه؟ از نظر عملکرد برنامه، همیشه فاجعه رخ نمیده اما هزینه برنامه بالا میره، در واقع اون موقع شما فلسفه راست رو رعایت نکردید و کدتون از حالت idiomatic خارج میشه.
پس جایی هزینه انتقال رو پرداخت میکنیم که لازمه. به عبارتی میشه گفت: در Rust معمولاً تا وقتی فقط نیاز به خواندن متن داریم از &str استفاده میکنیم و فقط زمانی سراغ Strting میریم که واقعاً به مالکیت داده یا تغییر محتوا نیاز داشته باشیم.
در زبان برنامه نویسی rust چه زمانی clone لازم میشه؟
قانون طلایی:
تا جای ممکن از آدرسدهی (
&) استفاده کن و فقط زمانی به سراغ.clone()برو که مجبوری.
خب روش انتخاب (کدام راهکار برای کجا؟):
- اگر میخوام بخونم: &str یا &T
- اگر میخوام مالکیت رو منتقل کنم به یه صاحب جدید داده و خودم بی خیالش بشم: move
- اگر دیدم مجبورم مالکیت رو منتقل کنم و بعدش استفاده داشتم هیچ راهی هم برای جلوگیری از این موضوع نداشتم: clone()
نکته مهم: قانون بالا در ۹۰ درصد مواقع به خوبی کار میکند، اما یه چیزی باید یادتون باشه وقتی کدی وجود داره که استفاده از اون در شرایط استفاده زیر فشار با تعداد بالای اجرا (High Throughput / Performance-critical) هستش باید حساسیتون بیشتر بشه و به این فکر کنید آیا نیاز هستش ساختار و روند رو جوری بازنویسی کنم نیاز به clone نباشه؟ یا از RC, Arc استفاده کنم که در ادامه بهش پرداختم.
قانون انتخاب بین حلقه یا iterator در rust
متن ساده:
- for ساده استفاده کن
- collect و …)
- کد کوتاهتر، ایمنتر و idiomatic تولید میکنن و میشه باهاشون lazy evaluation داشت، ولی بعضی وقتها حلقه ساده خواناتر است.
یه سوال مهم دیگه: اگر واقعا نمیدونستی کدوم رو انتخاب کنی؟ اون موقع for رو استفاده کن.
انتخاب انواع iterator و مالکیت ownership در زبان rust
اینو همیشه یادتون باشه rust یک حاکم ظالمه که دوست داره از شما بابت نقل و انتقال مالیت بگیره 😁 یعنی چی این جمله:
کمترین میزان مالکیت (ownership) را که نیاز داری بگیر!
وگرنه باید مالکیت بپردازی 😉 یه جاهایی کامپایلر راه نمیاد یه جاهایی که کامپایلر راه بیاد کد شما از حالت ایده (idiomatic) خارج شده و کامیونتی باهات راه نمیاد 🙃 شما گاهی اوقات میشه هزینه بیشتر به کامپایلر تحمیل کنی اما داری جامعه rust رو عصبانی میکنی این چند تا جمله رو گفتم تا برسیم سر قانون طلایی:
- فقط خواندن؟ →
.iter() - تغییر دادن عناصر؟ →
.iter_mut() - مصرف کردن و گرفتن مالکیت عناصر؟ →
.into_iter()
توضیح ۱: وقتی فقط میخوای دادهها را بخونی و هیچ تغییری تو دادهها نداری و انتقال مالکیت نداری
توضیح ۲: وقتی میخوای تو عناصر داده تغییرات ایجاد کنی نه انتقال مالکیت
توضیح ۳: وقتی دیگه کار تمام شده و میخوای همه چیزو بکشی بالا تمام 😁
اگر گیر کردم ندوستم کدومو انتخاب کنم چی؟
اول
iter()بنویس، بعد اگر کامپایلر گفت ریدی، به سطح بالاتر برو 😁
طول عمر یا لایف تایم در برنامه نویسی rust
اولش بگم آخ بسوزه پدر لایف تایم که لایف تایم عمر منو گرفت اون زمان 😁
- &T) نباید بعد از دادهای که بهش اشاره میکنه استفاده شود.
- داده صاحب اصلی (owner) باید تا آخرین استفاده از reference زنده باشد.
- اگر میخوای reference رو برگردانی از یک تابع، باید lifetime ورودی رو به خروجی گره بزنی یا داده رو منتقل کنی (move) یا clone کنی.
یه سوال مهم دیگه: اگر واقعا نمیدونستی کی باید لایف تایم بنویسی ننویس کامپایل خودش بهت گیر میده ولی وقتی بهت گیر داده میگه:'static بذار که معمولا این انتخاب درست نیست چون معمولا به این معنی طول عمر این برابر با کل برنامه هست.
استفاده از (*) dereference یا ارجاع زدایی در rust
تا وقتی میتوانی با reference (
&Tیا&mut T) کار کنی، dereference نکن. فقط وقتی*بزن که واقعاً به خودِ مقدار (T) نیاز داری.یا بهتر بگیم این سوال رو از خودمون بپرسیم: آیا این کد به خودِ مقدار نیاز دارد یا فقط به آدرس/borrow آن؟
اگر شک کردیم چه کنیم؟
اول بدون
*بنویس. اگر کامپایلر گفت بهTنیاز دارم ولی&Tداری، آنوقت*اضافه کن.
انتخاب بین let if و match
اگر فقط به یک pattern خاص اهمیت میدی از
if letاستفاده کن؛ اگر همهٔ حالتها برات مهم هستن ازmatchاستفاده کن
جدول انتخاب:
| وضعیت | انتخاب |
|---|---|
| فقط یک pattern مهم است | if let |
| چند pattern مهم هستند | match |
| branch دیگر را نادیده میکنی | if let |
| branch دیگر هم منطق دارد | match |
| شک داری | match |
این قانون تقریباً در ۹۰٪ مواقع شما رو به سمت کد idiomatic در Rust هدایت میکنه.
حالا سوال اینه اگر تو باتلاق انتخاب گیر کردیم چی؟ 😁
من این قانون را پیشنهاد میکنم:
در حالت شک عمیق،
matchرا انتخاب کن.
چون:
- به صورت جامع بررسی میکنه.
- بعداً اگر enum بزرگتر شد راحتتر توسعه پیدا میکنه.
- intent کد واضحترن
به عبارتی میشه گفت match هیچ وقت غیر idiomatic نیست شاید یه سری ها تو کامیونتی rust غر بزنن بابا توام کدت دلچسب نیست ولی نمیتونن ایراد جدی بگیرن 😉
where در امضای تابع در زبان راست
به صورت کلی where مینویسیم تا کد خواناتر شود.
fn print<T: Display>(value: T){
fn print<T>(value: T)
where
T: Display,
{
این دو تا کی هستند و تفاوتی ندارند اما نکته اما مهمترین نکته اش اینه که وقتی یک generic دارید و میخواین اونو محدودی کنید باید نوعش رو معرفی کنید حالا این به روش بالا یا پایین where میشه.
رسیدیم به قسمت سخت ماجرا اشاره گر های هوشمند تغییر داده و فانتوم data وبقیه ماجرا ها که گیر اصلی معمولا تو ایناست با طرح مشکل قانون طلایی رو مطرح میکنیم:
استفاده از اشاره گرهای هوشمند و برنامه نویس همزمان در Rust
مقدمه لایه صفر: Rust ایدهآل
میشه گفت Rust دوست داره همه چیز این شکلی باشد، یک مالک بیشتر نداشته باشه بقیه غلط بکنن فضولی بکنند تو کار داده 😁
مشکل اول: چند نفر میخواهند مالک باشند؟
که این اتفاق معمولا در برنامههای gui زیاد میوفته:
هر دو نیاز دارند زنده نگهش دارن، اینجا:Rc<T>میاد. جفتک میندازه 😂
سوال: آیا Rc اجازه mutation یا تغییر میده؟
عمرااااااااا
چون چند مالک وجود دارد.
Rust نمیتواند بفهمد کدام یکی الان دارد مینویسد.
مشکل دوم: چند مالک + نیاز به تغییرات
اینجا: Rc<RefCell<T>>متولد میشه تا به شما دسترسی بیشتری بدهد. حرف حسابشون چیه:
Rc میگه:
مالکهای متعدد
RefCell میگه:
borrow checker رو بفرست runtime به گیر نده سرجدت 😂
مشکل سوم: گراف داری و memory leak میگیری
به عبارت ساده تر یعنی تو در تو دارن مقادیر به صورت پدر و فرزند همه دیگرو صاحب میشن، اصن کار RC اینه که ببینه کی باید اینو تو حافظه از بین ببره واسه همین تعداد مصرف کننده رو میشماره اینجا چه به دنیا میادش: Weak
حرف حسابش چیه:
من صاحب (بچه😂) نیستم فقط آدرس رو میشناسم ( کارت تموم شد میام در خونتون😂 حافظه رو پس میگیرم)
مشکل چهارم: Multi Thread
اینجا دیگه RC به چوخ میره و میمیره 😂
جایگزین چه کوفتیه:
Arc<T>
اتمیک هستش بمب اتم نیست ولی همه جا یعنی بین تمام ریسمان ها Thread تعداد استفاده رو میشماره تا حافظه رو پس بگیره.
این مشکل خیلی باحاله:
مشکل پنجم: Arc دارم ولی mutation میخواهم.
اینجا: Arc<Mutex<T>>ظهور پیدا میکنه از اعماق غارهای تو در توی rust
Arc حرف حسابش چیه؟
مالکهای متعدد اونم از نوع اتمی که همه جا فضولی میکنه.
Mutex چیه:
فقط یک writer وجود داره بقیه دسته خر باید تماشا کنن تا کار اون نویسنده با داده تموم بشه😐😂
مشکل ششم: خواندن زیاد است.
یعنی تعداد فضولایی که به داده دسترسی دارن و مدام میخونن ببین چی توشه، در بین ریسمان ها زیاد هست مثلا ۱۰۰ خواننده داده اینجا Mutex میشه مثل صف نونوایی و RwLock<T> میشه مثل استدیوم همه دارن میبینن بازی کن با توپ چیکار میکنه ولی یه نفر فقط توپ دستشه و داره تغییرات ایجاد میکنه 😉
مشکل هفتم: فقط &self دارم.
یعنی mute &self نمیتونم داشته باشم اینجا cell یا refcell به کار میاد که از cell استفاده میکنم وقتی که داده ام حجمش مشخص هستش مثل اعداد و bool و … وقتی سنگین میشه مثل struct یا string اون موقع refcell باید استفاده بشه.
حرف حسابشون چیه:
- بردار
- عوض کن
- بگذار سرجاش
تمام
مشکل هشتم: type وجود دارد ولی فیلد وجود ندارد.
این واقعا یکی از شاهکار های عجیب rust هست شاید عجیب ترین قسمت که فانتوم دیتا اینجا متولد نمیشه اینجا خودشو به زور جا میکنه مثالشم از AI بپرسید بهتون میگه.
حرف حساب کامپایلر چیه:
مثلا: تو اصلاً T یا ‘a رو نگه نمیداری مگه من اسکول شدم که بیام ازت قبول کنم اینا رو
حرف حساب ما چیه:
من وابستهام پس اینو یه روح ( فانتوم در نظر بگیر) تو نمیفهمی من میفهمم:
کامپایلر میبیند:
_marker: PhantomData<T>
و میگوید:
حله داداش
پس این struct واقعاً به T وابسته است.
اگر واقعاً نمیدونستی Cell یا RefCell؟
اول RefCell.
بعد اگر دیدی فقط یک Copy type داری،
به Cell تبدیلش کن.
اگر واقعاً نمیدونستی Mutex یا RwLock؟
اول Mutex.
فقط وقتی مطمئن شدی
تعداد Readerها خیلی بیشتر از Writerهاست
برو سراغ RwLock.
خب تموم شد، شاید در طول زمان اینو بیشتر آپدیتش کنم یادم بیاد چیزی، اما یه سوال چالش برانگیز از شما می پرسم:
تفاوت Arc<Mutex> با Mutex<arc> چیه؟😉
اول از همه کوچک ترین عضو مجموعه 4xmen بنده حقیر هستم.
در ثانی یک توسعه دهنده هستم زمینه های تخصصی کارم :
Delphi, Rust ,PHP, JavaScript, Unreal engine, asm, Dart
و علاقه مند به لینوکس، RCE ، کریپتوگرافی هستم.
تا الان حضرت حق اینا رو به ما داده و هر وقت که بخواد ازمون میگیره دست خودشه.
یا حق، با حق، تا حق







دیدگاهتان را بنویسید لغو پاسخ