۶ اشتباه در برنامه نویسی Rust که نباید انجام دهید
زبان برنامه نویسی مورد علاقهی افراد برای ایمن سازی حافظه، خالی از ایراد نیست. شش اشتباه که هنگام نوشتن کد زبان برنامه نویسی Rust باید دقت کنید. زبان برنامه نویسی Rust به کاربران خود این امکان را تا بتوانند بدون جمع آوری دادههای نامربوط، برنامه را برای ایمنی حافظه بنویسند که با سرعت در سطح پایین و سخت افزار اجرا میشوند. همچنین کسب مهارت در این زبان آسان نیست و یادگیری مفاهیم اولیه آن بسیار پیچیده میباشد. در این محتوا به توضیح و تحلیل ۶ اشتباه در برنامه نویسی Rust که باید از آنها پرهیز کنید پرداختیم. پس تا انتها همراه ما باشید.
۶ نکته که در Rust باید بدانید:
- شما نمیتوانید Borrow Checker در Rust را «خاموش کنید».
- از خط فاصله ‘_’ برای متغیرهایی که به هم وابستهاند استفاده نکنید.
- توابع محلی یا Closures طول عمر مشابهی با توابع معمولی ندارند.
- وقتی مدت قرض دادن مالکیت دادهها تمام میشود، توابع Destructors ممکن است همیشه اجرا نشوند.
- به موارد نا امن و طول عمر بی انتهاب دادهها توجه کنید.
- کنترل رسیدگی به خطا بر عهده ی تابع ()unwrap. میباشد.
۱-شما نمی توانید Borrow Checker را «خاموش کنید».
مالکیت یا Ownership، قرض گرفتن مالکیت یا Borrowing، و طول عمر (Lifetime) مفاهیمی هستند که Rust آنها را باهم ادغام میکند. آنها بخشی جدایی ناپذیر از نحوهی حفظ ایمنی حافظه بدون جمع آوری دادههای نامربوط توسط Rust هستند.
خاموش کردن Borrow Checker
برخی از زبانهای دیگر ابزارهای بررسی کد را ارائه میدهند که به توسعهدهندگان برنامه در مورد مشکلات ایمنی یا حافظه هشدار میدهند. اما همچنان اجازه میدهند که کد کامپایل شود. (به زبان ماشین ترجمه شود). در Rust چنین چیزی وجود ندارد. در Borrow Checker (بخشی از کامپایلر Rust که مقدار تمام عملیات مالکیت را تأیید می کند) یک ابزار اختیاری نیست که بتوان آن را خاموش کرد. کدی که برای Borrow Checker معتبر نیست، Rust به هیچ وجه نمیتواند کامپایل کند.
کتاب Example By Rust
در مورد اینکه چگونه با خطای Borrow Checker مواجه نشویم ، یک مقاله کامل میتوان نوشت. خوب است بخش Rust By Example که در مورد محدوده يا Scoping (بخشی مهم در Ownership، Borrowing و Lifetime که وظایفی از جمله بررسی صحت مقدار قرض دادنها، ایجاد و از بین بردن متغیرها و … را دارد.) را بررسی کنید تا ببینید قوانین برای بسیاری از رفتارهای مرسوم چگونه هستند.
روش مدیریت مالکیت داده ها در Rust
در مراحل اولیه ی کار با زبان Rust، به یاد داشته باشید که همیشه با کپی کردن به وسیله ی تابع .clone() میتوان مشکلات مربوط به مالکیت را حل کرد. در بخشهایی از برنامههایی که برای اجرا به منابع زیادی نیاز دارند، کپی کردن به ندرت اثر قابل توجهی خواهد داشت. پس، روی قسمتهایی تمرکز کنید که کمترین نیاز را به کپی کردن داشته باشند و نحوهی کارآمدکردن Borrowing و Lifetime را در آن قسمتها را متوجه شوید.
۲-از خط فاصله ‘_’ استفاده نکنید برای متغیرهایی که به هم وابستهاند.
متغیر _ (زیر خط یا خط فاصله) رفتار به خصوصی در Rust دارد. به این معنی است که مقدار داده شده به متغیر به آن محدود نمیشود. این معمولاً زمانی قابل استفاده است که مقادیر دریافتی بلافاصله نادیده گرفته شوند. برای خاموش کردن اخطار به عنوان مثال اخطار must_use ، میتوان یک ـ به آن اختصاص داد.
مفهوم Statement و Scope در Rust
بدین منظور، استفاده از زیر خط برای مقدار دهی که فراتر از Statement (به واحد های عملیاتی در کد اشاره دارد که یک عمل را انجام میدهند ولی به عنوان یه مقدار بر نمیگردند.) میرود را انجام ندهید. توجه داشته باشید که بحث در مورد Statement است، نه Scope. (محدوده یا Scope که ناحیه یا بخشی از کد است که در آن متغیرها، توابع، یا سایر نمادها تعریف و مورد استفاده قرار می گیرند. زمانی که یک متغیر یا شیء از Scope خارج میشود، عمر آن به پایان میرسد و ممکن است در scope حافظه ی مربوط به آن مورد آن آزاد شود.)
مواردی که باید مراقب آنها باشید، آنهایی هستند که می خواهید دادهای را نگهدارید تا زمانی که از Scope خارج شود. اگر شما یک بلوک کد مانند زیر داشته باشید:
let _ = String::from(" Hello World ").trim();
زمانی که یک String یا رشته ایجاد میشود به سرعت از Scope خارج میشود بعد از Statement تا پایان بلوک نگه داشته نمی شود. (روش فراخوانی یا Call برای اطمینان از حذف نشدن نتایج در کامپایل کردن که همان تبدیل به زبان ماشین است.)
آسان ترین راه برای جلوگیری از این مشکل این است که فقط از نامهایی مانند _user یا _item برای اختصاص دادنها استفاده کنید که میخواهید تا پایان Scope ادامه داشته باشند، اما قصد ندارید برای چیزهای دیگری آنها را به کار ببرید.
۳-توابع محلی یا Closures طول عمر مشابهی با توابع معمولی را ندارند.
تابع زیر را درنظر بگیرید.
fn function(x: &i32) -> &i32 {
x
}
ممکن است بخواهید این تابع را به عنوان یک تابع Closure برای مقدار بازگشتی از یک تابع بیان کنید.
fn main() {
let closure = |x: &i32| x;
}
تنها مشکل این است… کد که کار نمی کند. کامپایلر با این خطا هشدار میدهد:
Life time may not live long enough
“طول عمر ممکن است به اندازه کافی نباشد”؛ زیرا مقادیر ورودی و خروجی در توابع محلی یا Closure باهم متفاوتاند.
یک راه برای عبور از این مشکل استفاده از مرجع Static است.
fn main() {
let _closure: &dyn Fn(&i32) -> &i32 = &|x: &i32| x;
}
استفاده از یک تابع جداگانه مرسومتر است. اما از این نوع مسائل جلوگیری میکند. این باعث میشود که توابع Closure به صورت بصری واضح تر و آسان تر تجزیه و تحلیل شوند.
۴-وقتی مدت قرض دادن مالکیت دادهها تمام میشود، توابع Destructors ممکن است همیشه اجرا نشوند.
مانند زبان ++C، زبان Rust به شما اجازه میدهد تا برای این گونه موارد،توابع Destructor ایجاد کنید که میتوانند زمانی که یک شی از Scope خارج میشود، برنامه کار کند. اما تضمینی برای اجرای درست تابع نمیدهد.
این نیز، شاید حتی در حد دو برابر بیشتر، برای زمانی که قرضدهی مالکیت یک شیء به پایان می رسد، صادق است. اگر قرض دادن مالکیت یک شیء به پایان برسد، لزوماً به این معنا نیست که تابع Destructor اجرا شده است.
در حقیقت، مواقعی وجود دارد که نمیخواهید تابع Destructor فقط به این دلیل زمان قرص دهی مالکیت تمام شده است، اجرا شود. به عنوان مثال، موقعی که در اشارهگر (Pointer) چیزی را نگه دارید.
۵-به موارد ناامن و طول عمر بی انتها دادهها توجه کنید.
کلمه کلیدی unsafe برای علامت گذاری کد Rust به کار برده میشود که میتواند کارهایی مانند دسترسی مستقیم به اشارهگرهای خام یا Raw Pointers (که مستقیم به حافظه اشاره میکنند) را بدهد. اگرچه در اغلب مواقع نیاز به این کار نیست ، ولی به محض اینکه انجام شود، با دنیای جدیدی از مشکلات احتمالی مواجه میشوید.
کتاب Rustonomicon در رابطه با امنیت حافظه
مثلاً، دسترسی مستقیم به یک اشارهگر خام تولید شده توسط یک عملکرد ناایمن منجر به طول عمر بدون انتها می شود. در کتاب Rustonomicon (کتابی در مورد برنامی نویسی Rust که به جزییات کار با برنامه نویسی نا امن صحبت کرده است)، در مورد عدم امنیت ها در Rust، اخطار میدهد که یک طول عمر بی انتها “در حد رفتار یه داده در محیط های متفاوت، اهمیت داشته باشد.” این بدان معناست که میتواند به طور شگفت انگیزی فراتر از آنچه در ابتدا به آن نیاز داشتید یا در نظر داشتید رشد کند.
اگر در مورد کاری که با یک مرجع نامحدود انجام میدهید وسواس دارید، نباید به مشکلی بر بخورید. اما محض احتیاط، بهتر است برای دسترسی مستقیم دادن به اشارهگره ها، آنها را در یک تابع قرار دهید و از طول عمر درمحدوده ی تابع استفاده کنید، به جای اینکه اجازه دهید آنها در داخل Scope یک تابع سرگردان باشند.
۶-کنترل رسیدگی به خطا بر عهده ی تابع ()unwrap. میباشد
هتگامی که یک عملکرد یک ٖٖresult (نتیجه) را برمیگرداند، دو روش اصلی برای مدیریت آن وجود دارد. یکی با تابع ()unwrap. یا یکی از مشابههایش (مانند ()unwrap_or.). دومی match Statement است. (یعنی این ساختار Match تمام موارد ممکن یا ضروری را پوشش میدهد و اغلب برای مدیریت شرایط پیچیده یا متغیرهای مختلف به کار میرود.) برای رسیدگی نتیجهی Err همان Error یا خطا به کار میرود.
فایدهی مهم تابع ()unwrap.
مزیت بزرگ ()unwrap. راحت بودن آن است. اگر در مسیر کدنویسی هستید که هرگز انتظار ندارید شرط خطا در برنامه ظهور کند، یا به هر حال مقابله با یک شرط خطا غیرممکن است، میتوانید از ()unwrap. استفاده کنید تا مقدار مورد نیاز خود را بدست آورید و کار خود را ادامه دهید.
اما یرای این، باید بهایی بپردازید. هر شرط خطا باعث Panic (زمانی رخ میدهد که برنامه با شرایطی مواجه میشود که نمیتواند آن را مدیریت کند و یک پیام خطا نمایش داده میشود) میشود و برنامه را متوقف میکند. Panic در Rust به دلایلی، غیرقابل بازیابی است: این مفهوم را دارد که یک مورد بسیار جدی پیش آمدهاست که یک باگ واقعی را در برنامه نشان میدهد.
محدودیت ظرفیت
اگر از ()unwrap. یا یکی دیگر از گونههای ()unwrap. مانند ()unwrap_or. استفاده میکنید، دقت کنید که فعلاً ظرفیت رسیدگی به خطاها محدود است. شما باید با مقداری از گونهای عبور کنید که تأیید کند نتیجه ی شما OK خواهد بود.( در دو حالت Err و OK که باید نتیجه OK باشد.) . با ساختار match، شما انعطاف پذیری رفتاری بسیار زیادی نسبت به گرفتن بازدهی از گونههای مناسب دارید.
اگر گمان نمیکنید که هرگز به این انعطافپذیری در یک مسیر برنامهتان نیاز نخواهید داشت، ()unwrap. مناسب است. با این حال، همچنان توصیه میشود که ابتدا full match بنویسید تا ببینید آیا برخی از جنبههای مدیریت خطا را نادیده گرفتهاید.
و در آخر
زبان برنامهنویسی Rust به خاطر ویژگیهای ایمنی حافظهاش محبوب است، اما نوشتن کد در آن بدون خطا آسان نیست. در هنگام برنامهنویسی با Rust، باید به نکاتی توجه کرد.
- اول، نمیتوانید Borrow Checker را خاموش کنید. زیرا بخشی حیاتی از کامپایلر است.
- تابع ()clone. برای کپی کردن مالکیتها کاربرد دارد
- دوم، از خط فاصله (_) برای متغیرهای وابسته استفاده نکنید، زیرا ممکن است باعث سردرگمی یا خطا شود.
- توابع Destructors که هنگام پایان عمر دیتا اجرا میشوند، همیشه بهدرستی عمل نمیکنند، بنابراین برای مدیریت دادهها در محدوده Scope باید دقت بیشتری به خرج دهید.
- همچنین، استفاده از کلمه کلیدی unsafe برای دسترسی به اشارهگرهای خام میتواند منجر به مشکلات امنیتی و طول عمر بیانتها شود.
- در نهایت، اگر از تابع ()unwrap. برای مدیریت خطاها استفاده میکنید، بدانید که ممکن است باعث Panic شود. بنابراین بهتر است برای کنترل خطاها از full match استفاده کنید.
امیدواریم از خواندن مقاله “6 اشتباه در برنامه نویسی Rust که باید از آنها پرهیز کنید” لذت کافی را ببرید. در صورت داشتن هرگونه سؤال در بخش نظرات با ما در ارتباط باشید.
سؤالات متداول
چرا توابع Destructors ممکن است همیشه اجرا نشوند؟
توابع Destructors ممکن است به دلیل شرایط سیستمعامل یا کامپایلر، مدیریت حافظه غیرمنتظره، یا مداخله سایر برنامهها بهطور کامل اجرا نشوند. این امر میتواند به دلیل رفتارهای خاص در اجرای سیستمهای برنامهنویسی یا عوامل خارجی رخ دهد.
چه زمانی باید از ()unwrap. در Rust استفاده کرد؟
تابع ()unwrap. برای دسترسی سریع به مقدار یک Result مناسب است، اما اگر شرط خطا رخ دهد، باعث Panic میشود. بهتر است از ()unwrap. زمانی استفاده کنید که مطمئن هستید خطا رخ نمیدهد یا برنامه باید در صورت خطا متوقف شود. در سایر موارد، استفاده از ساختار match برای مدیریت خطا ایمنتر است.
به این مقاله امتیاز دهید!
میانگین امتیاز 0 / 5. تعداد رأی ها : 0
هنوز هیچ رأیی داده نشده. اولین نفر باشید!
اولین دیدگاه را اضافه کنید.