یکی از چیزایی که زبان 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 در 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 ، کریپتوگرافی هستم.
تا الان حضرت حق اینا رو به ما داده و هر وقت که بخواد ازمون میگیره دست خودشه.
یا حق، با حق، تا حق







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