برای طرح سوال و دسترسی به آموزش ها  کانال ما در تلگرام بپویندید  

قانون‌های طلایی rust در تصمیم‌گیری انتخاب موارد پیچیده

یکی از چیزایی که زبان rust از سایر زبان‌ها متمایز می‌کنه ویژگی های خاص این زبان برنامه‌نویسی به ویژه بحث مالکیت و سخت‌گیری های مدیریت حافظه در rust ممکنه آزاردهنده باشه، با توجه به مطالعات و تجربیاتم در زمینه rust به این مطلب رو می‌نویسم که تا ببینیم چطور میشه از این‌ها استفاده کرد. و انتخاب های پیچیده رو در قالب یک طرح مفهومی پیاده کنیم. قانون طلایی هر چیزی رو با چاشنی نظر بیان می‌کنیم.

سعی می‌کنم مطلب رو از ساده به پیچیده تر طرح کنم:

انتخاب همیشه بین String, &str در rust

این انتخاب معمولا در پارامتر های ورودی توابع و ساختار struc استفاده مطرح میشه، قانون طلایی استفاده چیه؟

اگر سوال بالا رو نتونستم جواب بدم چه کار کنم؟ &str بذار اگر گیر کردی پس مجبوری String بذاری.

یه سوال مهم دیگه: اگر همیشه string استفاده کنم چی میشه؟ از نظر عملکرد برنامه، همیشه فاجعه رخ نمی‌ده اما هزینه برنامه بالا میره، در واقع اون موقع شما فلسفه راست رو رعایت نکردید، و کدتون از حالت idiomatic خارج میشه. پس جایی هزینه انتقال رو پرداخت می‌کنیم که لازمه. به عبارتی میشه گفت: در Rust معمولاً تا وقتی فقط نیاز به خواندن متن داریم از &str استفاده می‌کنیم و فقط زمانی سراغ Strting میریم که واقعاً به مالکیت داده یا تغییر محتوا نیاز داشته باشیم

 

در زبان rust چه زمانی clone لازم میشه

قانون طلایی:

تا جای ممکن از آدرس‌دهی (&) استفاده کن و فقط زمانی به سراغ .clone() برو که مجبوری.

خب روش انتخاب (کدام راهکار برای کجا؟):

  1. اگر می‌خوام بخونم: &str یا &T
  2. اگر می‌خوام مالکیت رو منتقل کنم به یه صاحب جدید داده و خودم بی خیالش بشم:‌ move
  3. اگر دیدم مجبورم مالکیت رو منتقل کنم و بعدش استفاده داشتم هیچ راهی هم برای جلوگیری از این موضوع نداشتم: clone()

نکته مهم: قانون بالا در ۹۰ درصد مواقع به خوبی کار می‌کند، اما یه چیزی باید یادتون باشه وقتی کدی وجود داره که استفاده از اون در شرایط استفاده زیر فشار با تعداد بالای اجرا (High Throughput / Performance-critical) هستش باید حساسیتون بیشتر بشه و به این فکر کنید آیا نیاز هستش ساختار و روند رو جوری بازنویسی کنم نیاز به .clone نباشه؟ یا از RC, Arc استفاده کنم که در ادامه بهش پرداختم

قانون طلایی انتخاب بین حلقه یا iterator در rust

متن ساده:

  1. for ساده استفاده کن
  2. collect و …)
  3. کد کوتاه‌تر، ایمن‌تر و idiomatic تولید می‌کنن و میشه باهاشون lazy evaluation داشت، ولی بعضی وقت‌ها حلقه ساده خواناتر است

یه سوال مهم دیگه: اگر واقعا نمی‌دونستی کدوم رو انتخاب کنی؟ اون موقع for رو استفاده کن

 

قانون طلایی انتخاب بین انواع iterator در rust

اینو همیشه یادتون باشه rust یک حاکم ظالم که دوست از شما بابت نقل و انتقال مالیت بگیره 😁 یعنی چی این جمله:

کمترین میزان مالکیت (ownership) را که نیاز داری بگیر

وگرنه باید مالیت بپردازی 😉 یه جاهایی کامپایلر راه نمیاد یه جاهایی که کامپایلر راه بیاد کد شما از حالت ایده (idiomatic) خارج شده و کامیونتی باهات راه نمیاد 🙃 شما گاهی اوقات میشه هزینه بیشتر به کامپایلر تحمیل کنی اما داری جامعه rust رو عصبانی می‌کنی این چند تا جمله رو گفتم تا برسیم سر قانون طلایی:

  1. فقط خواندن؟ → .iter()
  2. تغییر دادن عناصر؟ → .iter_mut()
  3. مصرف کردن و گرفتن مالکیت عناصر؟ → .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 باید استفاده بشه

حرف حسابشون چیه:

  1. بردار
  2. عوض کن
  3. بگذار سرجاش

تمام

مشکل هشتم: 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> چیه؟😉

 


انتشار

در

,

توسط

برچسب‌ها:

نظرات

دیدگاهتان را بنویسید

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