آموزش زبان برنامهنویسی Rust - قسمت۳: معرفی آرایه, تاپل, کاراکتر و مقادیر بولی
در قسمت قبلی با انواع دادههای عددی آشنا شدیم. حالا برای اینکه بتوانیم یک برنامهی واقعی با زبان Rust بنویسیم, باید با 4 نوع داده دیگر در زبان Rust آشنا شویم.
چند جلسه که جلوتر برویم و بیشتر با زبان Rust آشنا شویم, با هم یک پکیج(crate) واقعی برای این زبان مینویسیم. البته در جلسات بعدی باید کدهای زیادی بزنیم تا هم با ویژگیهای Rust آشنا شویم و هم از خواندن صرف مطالب حوصلهمان سر نرود.
خب برویم سراغ اصل مطلب. هنوز 2 نوع از انواع اسکالر باقی مانده اند. قبل از رفتن سراغ انواع ترکیبی, باید آنهارا یاد بگیریم.
شما میتوانید نسخهی ویدیویی این آموزش را از اینجا ببینید:
فهرست مطالب
نوع داده Boolean
مثل اکثر زبانهای دنیا, و برخلاف زبان c, زبان Rustیک نوع دادهای خاص برای ذخیرهسازی مقادیر بولی دارد. این نوع خاص تنها میتواند 2 مقدار داشته باشد: true
برای حالت درست و false
برای حالت غلط.
مثلاً در قطعه کد زیر, مقدار متغیّر a
درست و مقدار متغیّر b
غلط است:
let a: bool; // اینجا متغیّر فقط تعریف شده است. ولی مقدار دهی نشده است. let b = false; // اینجا متغیّر هم تعریف شده است و هم مقدار دهی. a = true; // اینجا متغیّر مقدار دهی شده است
مقادیر بولی در عبارات شرطی کاربرد دارند. با عبارات شرطی در جلسات بعدی آشنا خواهیم شد.
فقط این مسئلهرا بهخاطر داشته باشید که مقادیر بولی تنها ۱ بایت فضا اشغال میکنند. به همین دلیل در حالت عادی استفاده از آنها برای نشاندادن درستی و نادرستی بهصرفهتر از انجام این کار با اعداد یا دیگر انواع داده است. چون آنها فضای بیشتریرا در حافظه اشغال میکنند.
نوع داده کاراکتر
کاراکتر پایهایترین نوع داده الفبایی در زبان Rust است.
در این زبان کاراکترها داخل علامت نقل قول تنها(single quotation) یا همان '
قرار میگیرند. برخلاف رشتهها که بین علامتهای نقل قول دوتایی(double quotation) یا همان "
قرار میگیرند.
بهعلاوه همانطور که در جلسات ابتدایی دیدیم, زبان Rust به صورت پیشفرض از utf-8
پشتیبانی میکند. بنابراین کاراکترها میتوانند حروف انگلیسی, فارسی, چینی, ایموجوی و تقریباً هرچیزی که فکرشرا بکنید باشند.
برنامهی زیر کاراکترهای مختلفرا درون متغیّرها ذخیره میکند و در پایان آنهارا نمایش میدهد:
fn main(){ let a = 'e'; let b = '1'; let c = ''; // نیمفاصله let d = 'پ'; let e = '👀'; println!("{} {} {}{}{} {} ", a, b, d, c, d, e); }
وقتی بعد از کامپایل این برنامهرا اجرا کنید, با خروجی زیر روبهرو میشوید:
e 1 پپ 👀
اگر قبلاً با c یا cpp کار کرده باشید و یاد رنجهایی که برای استفاده از این کاراکترها باید میکشیدید افتادهاید, بهتر است یک لیوان آب بنوشید و بغضتانرا قورت بدهید. دیگر آن دوران سخت بهسر آمده است.
دادههای ترکیبی
آرایه
i16
داشته باشید و هم دادهای از نوع boolean
.let array_name: [Type; size] = [element0, element1, ...];
let
, اسم آرایه نوشته میشود(مثل هر متغیّر دیگری). بعد از علامت :
داخل براکت باز و بسته باید نوع و اندازه آن را مشخص کنیم.;
قرارداده میشود و پس از آن اندازهی آرایه میآید.i8
ذخیره خواهند شد. حاصل چیزی این شکلی خواهد شد:let a: [i8; 3] = [0, 0, 0];
let months = ["فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند"];
در این جا خود کامپایلر متوجّه میشود که ما یک آرایه به طول 12 از دادههای رشتهای داریم.
دسترسی به عنصرهای آرایه
fn main(){ let months = ["فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند"]; let first_month = months[0]; let last_month = months[11]; let third_month = months[2]; println!("{}", first_month); println!("{}", last_month); println!("{}", third_month); }
خروجی هم, همانطوری که انتظارشرا داریم, این شکلی خواهد بود:
فروردین اسفند خرداد
خب حالا فرض کنید میخواهیم اسم ماه هفتمرا عوض کنیم. برای این کار باید برنامهی زیر را اجرا کنیم:
fn main(){ let months = ["فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند"]; println!("ماه هفتم قبل از عمل: {}", months[6]); months[6] = "محمّدرضا علی حسینی"; println!("ماه هفتم بعد از عمل: {}", months[6]); }
حالا برنامهرا اجرا میکنیم:
cannot assign to indexed content `months[..]` of immutable binding --> src/main.rs:8:5 | 2 | let months = ["فروردین", "اردیبهشت", "خرداد", | ------ consider changing this to `mut months` ... 8 | months[6] = "محمّدرضا علی حسینی"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot mutably borrow field of immutable binding
fn main(){ let mut months = ["فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند"]; println!("ماه هفتم قبل از عمل: {}", months[6]); months[6] = "محمّدرضا علی حسینی"; println!("ماه هفتم بعد از عمل: {}", months[6]); }
این بار اگر برنامهرا اجرا کنیم دیگر به ارور نمیخوریم و به خوبی و خوشی خروجی زیر را دریافت میکنیم:
ماه هفتم قبل از عمل: مهر ماه هفتم بعد از عمل: محمّدرضا علی حسینی
#include <cstdio> int main() { int myArray[3] = {0, 1, 2}; printf("%d", myArray[3]); return 0; }
myArray
که طولش 3 است, ۰ تا 2 است. حالا ما هنگام پرینت خواستهایم که مقدار موجود در ایندکس(۳) که اصلاً جزو این آرایه نیست خروجی داده شود.1245487872
fn main(){ let my_array = [0, 1, 2]; println!("{}", my_array[3]); }
حالا اگر برنامهرا کامپایل و سپس اجرا کنیم با این صحنه روبهرو خواهیم شد:
error: index out of bounds: the len is 3 but the index is 3 --> src/main.rs:22:20 | 22 | println!("{}", my_array[3]); | ^^^^^^^^^^^ | = note: #[deny(const_err)] on by default
همانطور که میبینید کامپایلر جلوی کامپایل شدن این برنامه را میگیرد و میگوید که مقداری که برای index انتخاب کردهایم غیر قابل قبول است.
حالا اگر ما وقتی داریم با آرایهها کار میکنیم مقدار index را از کاربر برنامه دریافت کنیم، یعنی بررسی مقدار آن هنگام کامپایل امکانپذیر نباشد، در صورت اشتباه بودن index مثل زبان C مقداری نامشخص برگردانده نمیشود. بلکه به جایش برنامه اصطلاحاً panic میکند و اجرای آن متوقف میشود.
یعنی Rust هنگام اجرا هم درست بودن بازهی index آرایهها را بررسی میکند (حالا اینکه چطوری این کار را میکند بماند برای بعد).
مثلاً اگر در همین برنامهی بالا مقدار index را از کاربر بگیریم برنامه به درستی کامپایل میشود. امّا اگر هنگام اجرا مقدار ۳ را به عنوان index وارد کنیم، اجرای برنامه با پیام زیر متوقّف میشود:
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 3', src/main.rs:3:20 note: Run with `RUST_BACKTRACE=1` for a backtrace.
حالا اگر برنامهرا با Backtrace
که در خروجی قبلی پیشنهاد شده است اجرا کنیم, با متنی طولانی از شیوهی panic مذکور روبهرو میشویم:
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 3', src/main.rs:3:20 stack backtrace: 0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace at libstd/sys/unix/backtrace/tracing/gcc_s.rs:49 1: std::sys_common::backtrace::print at libstd/sys_common/backtrace.rs:71 at libstd/sys_common/backtrace.rs:59 2: std::panicking::default_hook::{{closure}} at libstd/panicking.rs:211 3: std::panicking::default_hook at libstd/panicking.rs:227 4: std::panicking::rust_panic_with_hook at libstd/panicking.rs:463 5: std::panicking::begin_panic_fmt at libstd/panicking.rs:350 6: rust_begin_unwind at ibstd/panicking.rs:328 7: بقیهی متنرا چون طولانی بود حذف کردم. خودتان با اجرای برنامه امتحان کنید
چیزی که دیدید جلوهای از زیباییهای Rust بود! این زبان قول داده که ایمنی حافظهی شمارا تأمین کند. به همین دلیل برخلاف c نمیگذارد که به بخشهای دیگر حافظه دسترسی داشته باشید.
روشی کوتاه برای ساخت آرایهای با عناصر یکسان
حالا خیلی وقتها، مثلاً موقع initialize کردن یک آرایه، ما میخواهیم که تمامی عناصر یک آرایه مقداری برابر داشته باشند.
در Rust میتوانیم به جای اینکه یک مقدار را به اندازهی طول آرایه تکرارکنیم، از سینتکس زیر استفادهکنیم که مثل یک shortcut برایمان همان کار را میکند:
[valueType;arrayLength]
مثلاً میخواهیم یک آرایه از نوع i32
بسازیم که ۷تا عنصر به مقدار ۰ دارد، برای این کار کد زیر را مینویسیم:
[0i32;7]
پرینتکردن یک آرایه
برای پرینتکردن یک آرایه، ما نمیتوانیم از ("{}")!println
استفادهکنیم. اگر این کاررا بکنیم با ارور مواجه میشویم. مثلاً کد زیر را ببینید:
fn main() { println!("my array is: {}", [10i8;10]); }
اگر این برنامهرا بخواهیم کامپایلکنیم، با ارور زیر مواجه میشویم:
error[E0277]: `[i8; 10]` doesn't implement `std::fmt::Display` --> src/main.rs:26:33 | 26 | println!("my array is: {}", [10i8;10]); | ^^^^^^^^^ `[i8; 10]` cannot be formatted with the default formatter | = help: the trait `std::fmt::Display` is not implemented for `[i8; 10]` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead = note: required by `std::fmt::Display::fmt` error: aborting due to previous error
درمورد trait ها بعداً صحبت خواهیمکرد، امّا همانطوری که در متن ارور توضیح داده شده است، باید به جای {}
از {:?}
یا {:#?}
استفاده کنیم. بنابراین برای اینکه بتوانیم یک آرایهرا پرینتکنیم، باید از کدی شبیه به کد زیر استفادهکنیم:
fn main() { println!("my array is: {:?}", [10i8;10]); }
حالا اگر این برنامه را کامپایل و اجراکنیم، خروجی همانچیزی خواهد شد که انتظارش را داشتیم:
my array is: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
تاپل
let tuple_name: (Type0, Type1, ...) = (Value0, Value1, ...);
:
داخل پرانتز به ترتیب نوع هر عنصر را مینویسیم. برای مقداردهی هم دوباره عناصر را بهترتیب درون پرانتز مینویسیم.let tup0: (i32, char, bool, f64); // تاپل و نوع عناصر را مشخص کردیم. ولی هنوز مقدار ندادهایم let tup1 = (1, true, "سلام", 9.99); // نوعرا مشخص نکردیم. چون با دادن مقادیر خود کامپایلر آنرا میفهمد. tup0 = (33, 'G', false, 9.87); // اینجا مقادیر مربوط به تاپل اول را به آن نسبت میدهیم
دسترسی به عناصر تاپل
.
مینویسیم.fn main(){ let tup = (1, true, "سلام", 9.99); println!("{}", tup.2); }
بعد از کامپایل و اجرا خروجی زیر را میبینیم:
سلام
fn main(){ let tup = (1, true, "سلام", 9.99); let (x, y, v, z) = tup; println!("x: {}, y: {}, v: {}, z: {}", x, y, v, z); }
خب حالا بعد از اجرا این خروجیرا میبینیم:
x: 1, y: true, v: سلام, z: 9.99
The form you have selected does not exist.
یا حرفهای شو یا برنامهنویسی را رها کن.
چطور میشود در بازار کار خالی از نیروی سنیور ایران، بدون کمک دیگران و وقت تلفکردن خودت را تبدیل به یک نیروی باتجربه بکنی؟
پاسخ ساده است. باید حرفهای بشوی. چیزی که من در این کتاب به تو یاد خواهم داد.
سلام. خسته نباشید بابت آموزش های خوبتون. چرا قسمت دوم لود نمیشه صفحه ش؟
فک میکنم مشکلی وجود داشته باشه. لطفا رسیدگی کنید. با تشکر.