آموزش زبان برنامه‌نویسی Rust – قسمت 2: انواع داده‌های عددی و عملگرهای آن‌ها

در جلسات قبلی با نحوه‌ی نصب Rust در سیستم‌عامل‌های مختلف, ساختار یک برنامه در این زبان, نحوه‌ی کامپایل و اجرای برنامه‌ها, سینتکس و مفاهیم متغیّرها و ثابت‌ها و تفاوت آن‌ها با هم آشنا شدیم.

حالا بعد از یک وقفه‌ی نسبتاً طولانی که در نوشتن این قسمت پیش آمد, زمان آن است که به سراغ انواع داده‌ها در زبان برنامه‌نویسی Rust برویم.

انواع داده در زبان Rust

در زبان Rust ما دو نوع داده مختلف داریم: عددی(Scalar) و ترکیبی(compound). از آنجایی که زبان Rust یک زبان statically typed است, یعنی نوع هر متغیّر یا ثابت باید در زمان کامپایل کاملاً مشخّص باشد, دانستن نوع داده های مختلف ضروری است.

به‌علاوه استفاده از یک نوع داده‌ی خاص ممکن است تأثیر خیلی مهمی روی کارایی نرم‌افزار ما داشته باشد.

در این قسمت فقط به نوع داده های عددی می‌پردازیم. در قسمت بعد با یکدیگر انواع دیگر را بررسی خواهیم کرد.

قبل از اینکه به نوع عددی در زبان Rust بپردازیم, یک مفهوم مهم در دنیای کامپیوتر را باید مرور کنیم.

https://www.youtube.com/watch?v=VJL5HFrhlUY&t=10s


اعداد علامت‌دار و بدون علامت

ما دو جور عدد داریم: اعدادی که علامتشان مهم است, یعنی مثبت و منفی دارند. و اعدادی که علامتشان مهم نیست و می‌توان همیشه آن‌هارا مثبت درنظر گرفت.

شاید بپرسید که نوع بدون علامت به چه دردی می‌خورد؟ تفاوت این دو عدد در نحوه‌ی ذخیره‌سازی آن‌ها است.

اعداد مثل هر داده‌ی دیگری به صورت صفر و یکی ذخیره می‌شوند. اسم هرکدام از این صفر و یک ها هم بیت است. برای اینکه بخواهیم مشخّص کنیم که عددی مثبت است یا منفی, مجبوریم که علامت آن‌را هم ذخیره کنیم. پس به یک بیت اضافی نیاز است. درنتیجه اعداد علامت‌دار فضای بیشتری را در حافظه اشغال می‌کنند.

بگذارید با یک مثال جلو برویم. نحوه‌ی نمایش بدون علامت عدد 2 به صورت دودویی اینگونه است: 10. حالا اگر بخواهیم عدد 2+ را نمایش بدهیم, باید آن‌را به شکل 010 نمایش دهیم. آن صفری که قبل از 1 آمده است یعنی اینکه این عدد مثبت است.

اگر هم بخواهیم عدد 2- را نمایش دهیم حاصل 110 خواهد شد. معنای چپ‌ترین 1 این است که این عدد منفی است.

حالا در کامپیوتر ما مجبوریم که برای نمایش اعداد از بخش‌های هم‌اندازه‌ای از حافظه استفاده کنیم. مثلاً اعداد را در بخش‌های 8 بیتی نمایش دهیم.

وقتی عددی بدون علامت است, از همه‌ی این 8 بیت برای نمایش آن عدد می‌توان استفاده کرد. یعنی می‌توان اعداد 0 تا 255 را نشان داد.

امّا وقتی عدد علامت‌دار است, چپ‌ترین بیت به نمایش علامت عدد اختصاص داده می‌شود. پس برای خود عدد تنها 7 بیت باقی می‌ماند. با این حساب می‌توان اعداد 128- تا 127 را نشان داد. همانطور که می‌بینید اندازه‌ی اعدادی که می‌توان نمایش داد کاهش می‌یابد.

به‌طور کلّی استفاده از اعداد بدون علامت وقتی که داده‌ی منفی نداریم راه بهتری است. چون می‌توانیم با فضای یکسان اعداد بیشتری‌را ذخیره کنیم.

حالا که تفاوت اعداد علامت‌دار و بدون علامت را فهمیدیم, وقت آن است که به سراغ زبان Rust برویم.

اعداد صحیح(Integer) در زبان Rust

اعداد صحیح اعدادی هستند که قسمت اعشاری ندارند و مقدارشان مشخّص است. در زبان Rust دو نوع کلّی از اعداد صحیح وجود دارند: اعداد صحیح علامت‌دار(Signed)  و اعداد صحیح بدون علامت(Unsigned).

فرمت کلّی برای یک نوع داده integer در زبان Rust این طوری است:

Type size

در جدول زیر 6 نوع مختلف برای اعداد علامت‌دار و بدون علامت نمایش‌داده شده اند:

همانطوری که می‌بینید علاوه بر حالاتی که خودمان دقیقاً اندازه‌ی عدد را انتخاب می‌کنیم, مقادیر isize و usize هم وجود دارند که از معماری سیستم پیروی می‌کنند. اگر معماری ماشین شما 64 بیتی باشد, اندازه عدد 64 بیت خواهد بود. اگر معماری 32 بیتی باشد, اندازه‌ی عدد هم 32 بیت خواهد بود.

مقدار پیش‌فرضی که Rust برای اعداد صحیح درنظر می‌گیرد i32 است. اگر یادتان باشد, قبلاً دیدیم که خیلی وقت‌ها لازم نیست ما به صورت صریح نوع متغیّرها را مشخّص کنیم و خود کامپایلر از مقداری که آن متغیّر گرفته است نوع‌را تشخیص می‌دهد.

در قطعه کد زیر چند مثال از متغیّرهایی با نوع‌های مختلف را می‌بینیم:

let  a: u۶۴ = ۱۰۲۴;
let b: i۸ = -۷;
let c : usize = ۸۰۰;
let d = -۶۴;          // به صورت پیش‌فرض نوع این متغیّر برابر با i۳۲ در نظر گرفته می‌شود

نمایش مقادیر عددی

در زبان Rust شما می‌توانید اعداد را در مبنای 10, 8, 16 و 2 نمایش دهید. همچنین شیوه‌ی به‌خصوص بایت هم وجود دارد که می‌تواند در متغیّرهایی از نوع u8 کاراکترها را ذخیره کند.

همچنین برای افزایش خوانایی می‌توانید بین ارقام علامت _ را قرار دهید.

به مثال‌های زیر توجّه کنید:

let a = ۱۲۳_۴۵۶;
let b = ۰xf۲; //  hexadecimal
let c = ۰o۷۱;	// octal
let d = ۰b۱۱۱۰_۰۰۰۱;	// binary
let c = b'C';	// byte

محدوده‌ی هر نوع و سرریز(Overflow)

هر نوع عددی با توجّه به علامت‌دار بودن یا نبودن و اندازه‌ی آن می‌تواند محدوده‌ی خاصی از اعداد را نمایش دهد. اگر عددی کوچک‌تر یا بزرگتر از آن محدوده را به آن نسبت دهیم سرریز(overflow) رخ می‌دهد. یعنی چون نمی‌توانیم مقدار کامل‌را نمایش دهیم, مقداری که نمایش‌داده می‌شود اشتباه خواهد بود.

در جدول زیر حداقل و حداکثر مقداری که هر نوع داده نگهداری می‌کند را می‌بینید:

حالا اگر overflow رخ دهد چه اتّفاقی می‌افتد؟ بیایید امتحان کنیم.

یک برنامه می‌نویسم و در آن مقدار 255 را به متغیّری از نوع i8  نسبت می‌دهیم:

fn main() {
    let a:i۸ = ۰xf_f;
    println!("{}\n", a);
}

وقتی این برنامه‌را اجرا کنیم می‌بینم که کامپایلر به ما warning زیر را نشان می‌دهد:

warning: literal out of range for i۸
 --> src/main.rs:۲:۱۶
  |
۲ |     let a:i۸ = ۰xf_f;
  |                ^^^^^
  |
  = note: #[warn(overflowing_literals)] on by default
  = note: the literal `۰xf_f` (decimal `۲۵۵`) does not fit into an `i۸` and will become `-۱i۸`
  = help: consider using `u۸` instead

همانطور که در متن پیام warning می‌بینید, کامپایلر به ما هشدار می‌دهد که overflow رخ خواهد داد و پیشنهاد می‌کند که از نوع به‌جای استفاده کنیم.

یک اتّفاق مهمی که اینجا افتاده است و باید به آن توجّه کنید این است که کامپایلر به‌جای error دادن تنها به ما یک warning داده است. مهم‌ترین تفاوت warning با error در این است که warning ها برخلاف error ها جلوی کامپایل و اجرا شدن برنامه‌را نمی‌گیرند. یعنی با وجود رخ‌دادن overflow برنامه‌ی ما همچنان اجرا خواهد شد.

وقتی که برنامه‌را اجرا کنیم می‌بینیم که به جای مقدار ۲۵۵ که انتظارش را داشتیم, عدد ۱- چاپ شده است.

رخ‌دادن overflow اتّفاق نسبتاً رایجی است و باید خیلی حواستان به آن باشد. اگر در حالتی متغیّری overflow کند, تمامی روند برنامه‌تان مختل می‌شود و نتیجه‌ای که کاربر می‌بیند اشتباه می‌شود.

بزرگترین مشکل overflow این است که عموماً در حالات خاصی از روند اجرای برنامه رخ می‌دهد, به همین دلیل شما هنگام نوشتن برنامه از درستی کارکرد آن مطمئن هستید, امّا هنگامی که برنامه به دست کاربران می‌افتد می‌بینید که خیلی‌ها به خاطر کارکرد اشتباه برنامه شاکی شده اند.

اعداد اعشاری(floating point) در زبان Rust

خب کارمان با اعداد صحیح تمام شد. پس نوبتی هم که باشد, نوبت اعداد اعشاری است. اعدادی که از دو بخش «جزء صحیح» و «اعشار» تشکیل می‌شوند.

در زبان Rust ما تنها 2 نوع عدد اعشاری داریم.  f32 و f64. همانطوری که حدس می‌زنید حرف f مخفف floating point است و عدد مقابل آن هم اندازه‌ی عدد را نمایش می‌دهد.

از آنجایی که در اکثر دستگاه‌های امروزی سرعت پردازی اعداد اعشاری 32 بیتی و 64 بیتی تفاوت چندانی ندارد, مقدار پیش‌فرض در زبان Rust نوع f64 است. چون با سرعت پردازش برابر دقّت بیشتری در نمایش اعشار دارد. ساده‌ترش را بخواهید یعنی تعداد ارقام اعشاری بیشتری را نمایش می‌دهد.

let b: f32 = 2.95;  
let a = 2.95;	// به صورت پیش‌فرض نوع متغیّر برابر با f64 درتظر گرفته می‌شود

عملگرهای انواع عددی

روی مقادیر عددی می‌توان عملیات جمع, تفریق, ضرب, تقسیم و mod(باقی‌مانده)گیری را انجام داد. مثل اکثر زبان‌های دیگر برای جمع از علامت + , برای تفریق از علامت - , برای ضرب از علامت * , برای تقسیم از علامت / و برای باقی‌مانده گیری از علامت ٪ استفاده می‌شود.

فقط به خاطر داشته باشید که باید دو طرف عملگر هردو عدد صحیح یا عدد اعشاری باشند. شما نمی‌توانید یک عدد صحیح را با یک عدد اعشاری جمع کنید, آن‌ها را در هم ضرب کنید و... .

مثلاً برنامه‌ی زیر را اجرا می‌کنیم:

fn main() {
    let a = 5;
    let c = 2.5;
    let d = a - c;
    println!("{}\n", d);
}

وقتی بخواهیم این برنامه‌را کامپایل کنیم با ارور زیر مواجه می‌شویم:

error[E0277]: cannot subtract `{float}` from `{integer}`
 --> src/main.rs:4:15
  |
4 |     let d = a - c;
  |               ^ no implementation for `{integer} - {float}`
  |
  = help: the trait `std::ops::Sub<{float}>` is not implemented for `{integer}`

ندارد.

خب به نظر جلسه طولانی شد و بهتر است بقیه‌ی انواع داده‌ی موجود در زبان Rust را در جلسه‌ی بعد دنبال کنیم.

اگر سؤالی داشتید, جایی از نوشته گنگ بود یا در مورد این مجموعه پیشنهادی دارید خوشحال می‌شوم من و دیگر خواننداگان‌را در بخش نظرات آگاه کنید.

من این آموزش را ابتدا در وبلاگ شخصی‌ام منتشر کردم. برای دیدن آن و مطالب بیشتر روی این لینک کلیک کنید.


خواندن قسمت قبلی این آموزش در ویروگول:

https://virgool.io/Software/%D8%A2%D9%85%D9%88%D8%B2%D8%B4-%D8%B2%D8%A8%D8%A7%D9%86-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87%D9%86%D9%88%DB%8C%D8%B3%DB%8C-rust-%D9%82%D8%B3%D9%85%D8%AA-1-%D8%B4%D8%B1%D9%88%D8%B9-%DA%A9%D8%A7%D8%B1-%D8%A8%D8%A7-%D9%85%D8%AA%D8%BA%DB%8C%D9%91%D8%B1%D9%87%D8%A7-%D9%88-%D8%AB%D9%88%D8%A7%D8%A8%D8%AA-%D8%A8%D8%AF%D8%A7%D9%86%DB%8C%D8%AF-aehcwwhwjl5t


رفتن به قسمت ابتدایی و شروع از اول:

https://virgool.io/Software/%D8%A2%D9%85%D9%88%D8%B2%D8%B4-%D8%B2%D8%A8%D8%A7%D9%86-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87%D9%86%D9%88%DB%8C%D8%B3%DB%8C-rust-%D9%82%D8%B3%D9%85%D8%AA-0-%D9%85%D8%B9%D8%B1%D9%81%DB%8C-%D9%88-%D8%B4%D8%B1%D9%88%D8%B9-%D8%A8%D9%87-%DA%A9%D8%A7%D8%B1-nwqjyzch0lde