وضعیت کنونی Rust در حالت Async
قسمت هایی از حالت Async در Rust مشابه قسمت های عادی و معمولی sync با ثبات بالا و خیلی خوبی پشتیبانی و تضمین می شود. قسمت های دیگه هنوز به ثبات کافی نرسیده اند و در حال تفییر هستند. موارد زیر رو میتونید از Rust در حالت Async انتظار داشته باشید:
- عملکرد فوق العاده با سرعت بالا در زمان اجرا برای task ها و عملیات مرسومی که به صورت همزمان انجام می شوند.
- تعامل خوب با قسمت های پیشرفته زبان مثل Lifetime ها و Pin کردن.
- وجود یک سری محدودیت های سازگاری بین کد sync و async و بین runtime های مختلف.
- به خاطر تغییراتی که در حال حاضر روی Async داره صورت میگیره از نظر نگهداری کد باید دقت بیشتری کنید.
به طور خلاصه استفاده از Async در Rust سخت تر از حالت عادی و کد sync هست ولی در عین حال بهترین سرعت رو تو مواردی که راجبش صحبت کردیم بهمون میده، البته اینم باید گفت که Async دائما در حال پیشرفته بنابراین این مسائل تو طول زمان از بین میره.
پشتیبانی زبان و کتابخانه ها
در حالی که برنامه نویسی Async توسط خود Rust پشتیبانی میشه، بیشتر برنامه های Async وابسته به عملکر کتابخانه هایی هستن که جامعه برنامه نویسان Rust اونا رو نوشتن و در قالب crate ها نگهداری میکنن. بنابراین شما باید رو ترکیبی از ویژگی های خود زبان و کتابخانه های موجود برنامه های Async تون رو جلو ببرید:
- بیشتر trait های پایه ای، type ها و توابع Async مثل
Future
که توسط کتابخانه اصلی و استاندارد Rust فراهم شده. - کلیدواژه های
async/await
توسط خود کامپایلر Rust پشتیبانی میشه. - خیلی از type ها، ماکرو ها و توابع توسط پیکیجی به اسم
futures
فراهم شده. که میشه ازشون داخل هر برنامه Async که با Rust نوشته شده استفاده کرد. - اجرا کردن کد async، IO ها و ساخت task های جدید توسط runtime های async صورت میگیره. دو تا از این runtime ها Tokio و async-std هستن. خیلی از برنامه های Async و کتابخانه های Async داخل crate به یک runtime خاص وابستگی دارن. برای جزئیات بیشتر بخش "اکوسیستم کد Async" رو مطالعه کنید.
خیلی از ویژگی های زبان که عادت داشتید توی کد معمولی sync استفاده کنید هنوز داخل Async موجود نیستن. این نکته هم قابل ذکر هست که Rust اجازه نمیده توابعی از نوع Async داخل trait ها تعریف کنید، در عوض برای اینکه به نتیجه ای که میخواید برسید باید روش ها و الگو های کمی پیچیده تری رو طی کنید.
کامپایل و خطایابی (Debuging)
برای بیشتر قسمت ها، خطاهای مربوط به کامپایلر و runtime توی کد async دقیقا مشابه حالت عادی توی Rust کار میکنه. البته یک سری نکاتی وجود داره که مطرح کردنش خالی از لطف نیست:
خطاهای کامپایل
خطاهای کامپایل در حالت Async در Rust دقیقا مشابه حالت عادی و sync خیلی دقیق و طبق استاندارد های همیشگی هستن، ولی از اونجایی که حالت Async معمولا وابسته به ویژگی های پیچیده تر زبان مثل Lifetime ها و Pin کردن هست، احتمال این که از این نوع خطا ها موقع نوشتن برنامه های Async بکنید بیشتره.
خطاهای Runtime
هر موقع کامپایل به یک تابع از نوع Async بر بخوره، یک state یا یه متغیر برای وضعیتش تو زیرساخت خودش براش در نظر میگیره. اطلاعاتی که برای ردیابی یه خطا وجود داره (Stack traces) معمولا اطلاعاتی از این state ها به ما میده، همچنین توضیح میده توسط چه توابعی داخل runtime به مشکل خورده. بنابراین تفسیر این خطاها شاید کمی بیشتر از کد های معمول و sync ما رو درگیر کنه.
حالت های جدید Fail شدن برنامه
چند حالت جدید برای به مشکل خوردن و fail شدن برنامه های Async وجود داره، برای مثال اگه یک تابعی که باعث block شدن منابع خاصی میشه رو به صورت Async صدا بزنید یا مفاهیم Future
رو اشتباه پیاده سازی کنید احتمال داره برنامتون به شکل های جدیدی که قبلا نبوده fail بشه.
این خطاها و مشکلات ممکنه خیلی بی سرو صدا قوانین کامپایلر و حتی unit تست هایی که نوشتید رو رد کنه و هیچکس بهشون گیر نده. درک عمیق از مفهوم های پایه ای Async که دقیقا کاریه که این کتاب براتون انجام میده باعث میشه این مشکلات تو برنامتون به وجود نیاد.
ملاحظاتی که باید موقع سازگاری کد بهش توجه بشه
کد Async و Sync نمیتونن همیشه خیلی آزادانه با هم ترکیب بشن. برای مثال، شما نمیتونید یک تابع Async رو مستقیما از داخل یک تابع sync صدا بزنید. در ضمن معمولا نوشتن کد sync و Async سبک ها و الگو های طراحی متفاوت از همی رو میطلبه، که همین مسئله میتونه اجرا کردن کد توی محیط های مختلف رو دشوار تر کنه.
حتی خود کد های Async هم همیشه نمیتونن آزادانه با هم ترکیب بشن. بعضی از کتابخانه ها و پکیج های crate وابسته به یک runtime خاص هستن تا کار کنن. که البته اگه اینطوری باشه باید داخل لیست وابستگی های اون crate این مورد رو مطرح کرده باشه.
این مشکلات مربوط به سازگاری کد با کتابخانه ها و محیط های مختلف دست شما رو انتخاب هاتون بسته تر میکنه، پس مطمئن حتما قبلش مطمئن شوید که راجب crate ها و runtime های مختلف تحقیقاتتون رو کردید. زمانی که یه rumtime مشخص رو انتخاب کردید، نیازی نیست نگران سازگاری کدتون باشید.
ویژگی های عملکردی از نظر سرعت
سرعت و عملکرد کد Async در Rust وابسته به runtime ای هست که ازش استفاده میکنید. اگرچه runtime هایی که قدرت Async به Rust میدن تقریبا جدید هستن، واقعا برای اکثر مواقع و کار ها عملکرد خیلی خوبی دارن.
بیشتر اکوسیستم های Async شرایط multi-thread رو توی runtime هایشون در نظر میگیرن. این باعث میشه لذت بردن از مزایای تئوری که در شرایط single-thread ای وجود داره سخت تر بشه. یکی دیگه از مواردی که بهش توجهی نشده عملیات و task هایی هستن که حساس به زمان هستن و تاخیر اجرا توشون خیلی مهمه، که واقعا مورد مهمی برای درایور ها یا برنامه هایی که رابط گرافیکی (GUI) دارن حساب میشه. اینجور برنامه ها نیاز دارن تا runtime ها یا/و سیستم عامل قابلیت زمان بندی دقیق اجراشون رو داشته باشه. میتونید در آینده انتظار کتابخانه های بهتری برای پشتیبانی از این موارد داشته باشید.