در Rust، نسبت دادن یک مقدار به یک اسم را «اتصال» میگویند. اتصالها تغییرناپذیرند، مگر آنکه با کلیدواژهی mut تعریف شده باشند. چون Rust زبانی با نوع ایستا است، هر اتصال نوعی دارد که در زمان کامپایل معلوم است.
اتصالها را بیشتر وقتها با کلیدواژهی let تعریف میکنند. مشخص کردن نوع یک اتصال برای بیشتر اتصالها اختیاری است، چون استنتاج نوع در Rust معمولاً میتواند نوع را بر اساس مقدارشان استنباط کند. یک اتصال چنین شکلی دارد:
// Automatically inferred type
let fingers = 10;
توابع آیتم هستند. اتصالها معمولاً به یک مقدار خاص اشاره میکنند، اما آیتمها به یک واحد سازماندهی کد اشاره دارند، معمولاً یک تابع یا یک ماژول، که در سراسر عمر برنامه در دسترس است. یک تابع بهطور خودکار نتیجهی آخرین عبارت خود را برمیگرداند. یک تابع میتواند صفر یا چند پارامتر داشته باشد، که اتصالهایی با عمر فراخوانی تابع هستند.
استنتاج نوع برای توابع از نظر تئوری ممکن است، اما بهعنوان یک انتخاب آگاهانه در طراحی زبان غیرفعال شده است. گرچه این یعنی هنگام نوشتن کد باید کمی وقت بیشتری صرف کنید تا دقیقاً مشخص کنید نوع ورودی و خروجی یک تابع چیست، در عوض هنگام خواندن کد در وقت صرفهجویی میکنید، چون همهی نوعهای ورودی و خروجی بهصراحت تعریف شدهاند.
fn add(x: i32, y: i32) -> i32 {
x + y
}
فراخوانی یک تابع با نوشتن اسم آن و سپس پرانتزها انجام میشود. اگر تابع پارامتر بخواهد، باید برای هرکدام داخل پرانتزها یک آرگومان مشخص شود.
let five = add(2, 3);
اگر نوع یک اتصال قابل استنباط نباشد، کامپایلر خطا گزارش میدهد. برای رفع این مشکل، یک حاشیهنویسی نوع صریح به اتصال اضافه کنید.
// Explicit type annotation
let fingers: i32 = 10;
آیتمها در Rust را میتوان پیش از تعریف شدن یا پس از آن به کار برد، چون عمر ایستا دارند. در مقابل، اتصالها را فقط پس از تعریف شدن میتوان به کار برد. استفاده از یک اتصال پیش از تعریف شدنش به خطای کامپایل منجر میشود.
fn main() {
// `fn add` hasn't yet been defined, but that's perfectly ok
dbg!(add(3, 4));
}
fn add(x: i32, y: i32) -> i32 {
x + y
}
// this won't compile; `a` is used before its binding is defined
let b = a;
let a = x + y;
Rust برای تعریف یک حوزه از آکولاد ({}) استفاده میکند. اتصالی که داخل یک حوزه تعریف شود، نمیتواند از آن خارج شود.
let a = 1;
dbg!(a); // 1
{
// Here, we re-bind `a` to a new value, which is still immutable.
// This technique is called _shadowing_. The new binding is constrained to
// this anonymous scope. Outside this scope, the previous binding still
// applies.
let a = 2;
let b = 3;
dbg!(a, b); // 2, 3
}
// can't use `b` anymore because it is out of scope
// dbg!(b);
// The shadowed `a` in the inner scope above has fallen out of scope,
// leaving us with our original binding.
dbg!(a); // 1
آیتمهای Rust را اغلب در ماژولها سازماندهی میکنند. هر کریت بهطور ضمنی یک ماژول است، اما میتواند زیرماژولهای درونی با عمق دلخواه تعریف کند. یک ماژول کارکردهای مرتبط را گروهبندی میکند و با کلیدواژهی mod تعریف میشود.
mod calc_i32 {
fn add(a: i32, b: i32) -> i32 { a + b }
fn sub(a: i32, b: i32) -> i32 { a - b }
fn mul(a: i32, b: i32) -> i32 { a * b }
fn div(a: i32, b: i32) -> i32 { a / b }
}
Rust از دو نوع کامنت پشتیبانی میکند. کلیدواژهی // یک کامنت تکخطی را نشان میدهد؛ هر چیزی که پس از کلیدواژه تا پایان خط بیاید نادیده گرفته میشود. کلیدواژههای /* و */ یک کامنت چندخطی را نشان میدهند؛ هر چیزی که میان این دو کلیدواژه باشد نادیده گرفته میشود. ترجیح دادن کامنتهای تکخطی هم اصطلاحی است و هم روشی پسندیده.
Rust همچنین از کامنتهای مستند پشتیبانی میکند، که در مستندات تولیدشده توسط cargo doc ظاهر میشوند. کامنتهای مستند بیرونی با کلیدواژهی /// ساخته میشوند، که دقیقاً مثل کلیدواژهی // عمل میکند. آنها به آیتمی که پس از آنها میآید اعمال میشوند، مثل یک تابع:
/// The `add` function produces the sum of its arguments.
fn add(x: i32, y: i32) -> i32 { x + y }
کامنتهای مستند درونی با کلیدواژهی //! ساخته میشوند، که دقیقاً مثل کلیدواژهی // عمل میکند. آنها به آیتمی که آنها را در برگرفته اعمال میشوند، مثل یک ماژول:
mod my_cool_module {
//! This module is the bee's knees.
}
کامنتهای مستند میتوانند طول دلخواه داشته باشند و markdown داشته باشند، که در مستندات تولیدشده رندر میشود.
در این تمرین میخواهید کدی بنویسید که به شما کمک کند تا از کتاب آشپزی محبوبتان یک لازانیای عالی بپزید.
چهار وظیفه دارید که همه به زمان صرفشده برای پخت لازانیا مربوط میشوند.
اتصال expected_minutes_in_oven را تعریف کنید تا بررسی کنید لازانیا چند دقیقه باید در فر بماند. طبق کتاب آشپزی، زمان مورد انتظار ماندن در فر ۴۰ دقیقه است:
expected_minutes_in_oven()
// Returns: 40
تابع remaining_minutes_in_oven را تعریف کنید که تعداد دقایقی را که لازانیا تا الان در فر بوده است بهعنوان پارامتر میگیرد و بر پایهی زمان مورد انتظار ماندن در فر از تمرین قبلی، برمیگرداند که لازانیا چند دقیقهی دیگر باید در فر بماند.
remaining_minutes_in_oven(30)
// Returns: 10
تابع preparation_time_in_minutes را تعریف کنید که تعداد لایههایی را که به لازانیا اضافه کردهاید بهعنوان پارامتر میگیرد و برمیگرداند که چند دقیقه صرف آمادهسازی لازانیا کردهاید، با این فرض که آمادهسازی هر لایه ۲ دقیقه طول میکشد.
preparation_time_in_minutes(2)
// Returns: 4
تابع elapsed_time_in_minutes را تعریف کنید که دو پارامتر میگیرد: پارامتر اول تعداد لایههایی است که به لازانیا اضافه کردهاید و پارامتر دوم تعداد دقایقی است که لازانیا در فر بوده است. این تابع باید برگرداند که چند دقیقه صرف پخت لازانیا کردهاید، یعنی مجموع زمان آمادهسازی بر حسب دقیقه و زمانی که لازانیا تا این لحظه در فر گذرانده است.
elapsed_time_in_minutes(3, 20)
// Returns: 26