مسیرها برای اشاره به یک آیتم در درخت ماژول
برای نشان دادن به Rust که یک آیتم را در درخت ماژول کجا پیدا کند، از یک مسیر استفاده میکنیم، مشابه استفاده از مسیر هنگام پیمایش در یک فایلسیستم. برای فراخوانی یک تابع، باید مسیر آن را بدانیم.
یک مسیر میتواند به دو شکل باشد:
- یک مسیر مطلق مسیری کامل است که از ریشه جعبه (crate) شروع میشود؛ برای کدی که از یک جعبه (crate) خارجی میآید، مسیر مطلق با نام جعبه (crate) شروع میشود، و برای کدی که از جعبه (crate) فعلی میآید، با کلمه کلیدی
crate
شروع میشود. - یک مسیر نسبی از ماژول فعلی شروع میشود و از
self
،super
یا یک شناسه در ماژول فعلی استفاده میکند.
هر دو مسیر مطلق و نسبی با یک یا چند شناسه که با دو نقطه دوبل (::
) جدا شدهاند دنبال میشوند.
با بازگشت به لیستینگ 7-1، فرض کنید که میخواهیم تابع add_to_waitlist
را فراخوانی کنیم. این کار مشابه پرسیدن این است: مسیر تابع add_to_waitlist
چیست؟ لیستینگ 7-3 شامل لیستینگ 7-1 با حذف برخی از ماژولها و توابع است.
ما دو روش برای فراخوانی تابع add_to_waitlist
از یک تابع جدید، eat_at_restaurant
، که در ریشه جعبه (crate) تعریف شده است، نشان خواهیم داد. این مسیرها درست هستند، اما یک مشکل دیگر وجود دارد که مانع کامپایل این مثال به شکل فعلی میشود. بعداً توضیح خواهیم داد که چرا.
تابع eat_at_restaurant
بخشی از API عمومی جعبه (crate) کتابخانهای ما است، بنابراین آن را با کلمه کلیدی pub
علامت میزنیم. در بخش «آشکار کردن مسیرها با کلمه کلیدی pub
»، به جزئیات بیشتری درباره pub
خواهیم پرداخت.
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
add_to_waitlist
با استفاده از مسیرهای مطلق و نسبیبار اولی که تابع add_to_waitlist
را در eat_at_restaurant
فراخوانی میکنیم، از یک مسیر مطلق استفاده میکنیم. تابع add_to_waitlist
در همان جعبه (crate) تعریف شده است که eat_at_restaurant
در آن قرار دارد، که به این معنی است که میتوانیم از کلمه کلیدی crate
برای شروع مسیر مطلق استفاده کنیم. سپس هر یک از ماژولهای متوالی را شامل میکنیم تا به add_to_waitlist
برسیم. میتوانید یک فایلسیستم با ساختار مشابه تصور کنید: ما مسیر /front_of_house/hosting/add_to_waitlist
را برای اجرای برنامه add_to_waitlist
مشخص میکنیم؛ استفاده از نام crate
برای شروع از ریشه جعبه (crate) مانند استفاده از /
برای شروع از ریشه فایلسیستم در شل است.
بار دوم که تابع add_to_waitlist
را در eat_at_restaurant
فراخوانی میکنیم، از یک مسیر نسبی استفاده میکنیم. مسیر با front_of_house
شروع میشود، که نام ماژولی است که در همان سطح از درخت ماژول به عنوان eat_at_restaurant
تعریف شده است. اینجا معادل فایلسیستم استفاده از مسیر front_of_house/hosting/add_to_waitlist
است. شروع با نام ماژول به این معنی است که مسیر نسبی است.
انتخاب بین مسیرهای مطلق و نسبی
انتخاب بین استفاده از مسیر نسبی یا مطلق یک تصمیم است که بر اساس پروژه شما گرفته میشود، و به این بستگی دارد که آیا احتمال بیشتری دارد کد تعریف آیتم را به طور مستقل از یا همراه با کدی که از آیتم استفاده میکند جابجا کنید. برای مثال، اگر ماژول front_of_house
و تابع eat_at_restaurant
را به یک ماژول به نام customer_experience
منتقل کنیم، باید مسیر مطلق به add_to_waitlist
را بهروزرسانی کنیم، اما مسیر نسبی همچنان معتبر خواهد بود. با این حال، اگر تابع eat_at_restaurant
را به طور مستقل به یک ماژول به نام dining
منتقل کنیم، مسیر مطلق به فراخوانی add_to_waitlist
تغییر نمیکند، اما مسیر نسبی باید بهروزرسانی شود. ترجیح ما به طور کلی این است که مسیرهای مطلق را مشخص کنیم زیرا احتمال بیشتری دارد که بخواهیم تعریف کد و فراخوانی آیتمها را مستقل از یکدیگر جابجا کنیم.
بیایید سعی کنیم کد لیستینگ 7-3 را کامپایل کنیم و ببینیم چرا هنوز کامپایل نمیشود! خطاهایی که دریافت میکنیم در لیستینگ 7-4 نشان داده شدهاند.
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hosting` is private
--> src/lib.rs:9:28
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported
| |
| private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
error[E0603]: module `hosting` is private
--> src/lib.rs:12:21
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported
| |
| private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors
پیامهای خطا میگویند که ماژول hosting
خصوصی است. به عبارت دیگر، ما مسیرهای صحیح برای ماژول hosting
و تابع add_to_waitlist
داریم، اما Rust به ما اجازه نمیدهد از آنها استفاده کنیم زیرا به بخشهای خصوصی دسترسی ندارد. در Rust، تمام آیتمها (توابع، متدها، ساختارها، enumها، ماژولها و ثابتها) به صورت پیشفرض برای ماژولهای والد خصوصی هستند. اگر بخواهید آیتمی مانند یک تابع یا ساختار را خصوصی کنید، آن را در یک ماژول قرار میدهید.
آیتمهای موجود در یک ماژول والد نمیتوانند از آیتمهای خصوصی درون ماژولهای فرزند استفاده کنند، اما آیتمهای درون ماژولهای فرزند میتوانند از آیتمهای ماژولهای اجداد خود استفاده کنند. این به این دلیل است که ماژولهای فرزند جزئیات پیادهسازی خود را بستهبندی و پنهان میکنند، اما ماژولهای فرزند میتوانند زمینهای که در آن تعریف شدهاند را ببینند. برای ادامه مثال، قواعد حریم خصوصی را مانند دفتر پشتی یک رستوران تصور کنید: آنچه در آنجا میگذرد برای مشتریان رستوران خصوصی است، اما مدیران دفتر میتوانند همه چیز را در رستوران ببینند و انجام دهند.
Rust تصمیم گرفته است که سیستم ماژول به این صورت کار کند تا پنهان کردن جزئیات پیادهسازی داخلی به صورت پیشفرض باشد. به این ترتیب، میدانید کدام بخشهای کد داخلی را میتوانید تغییر دهید بدون اینکه کد بیرونی را خراب کنید. با این حال، Rust به شما این امکان را میدهد که بخشهای داخلی کد ماژولهای فرزند را به ماژولهای اجداد بیرونی با استفاده از کلمه کلیدی pub
عمومی کنید.
آشکار کردن مسیرها با کلمه کلیدی pub
بیایید به خطای لیستینگ 7-4 برگردیم که به ما گفت ماژول hosting
خصوصی است. ما میخواهیم تابع eat_at_restaurant
در ماژول والد به تابع add_to_waitlist
در ماژول فرزند دسترسی داشته باشد، بنابراین ماژول hosting
را با کلمه کلیدی pub
علامت میزنیم، همانطور که در لیستینگ 7-5 نشان داده شده است.
mod front_of_house {
pub mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
hosting
به عنوان pub
برای استفاده از آن در eat_at_restaurant
متأسفانه، کد در لیستینگ 7-5 همچنان به خطاهای کامپایلر منجر میشود، همانطور که در لیستینگ 7-6 نشان داده شده است.
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:9:37
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:12:30
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors
چه اتفاقی افتاد؟ اضافه کردن کلمه کلیدی pub
در جلوی mod hosting
ماژول را عمومی میکند. با این تغییر، اگر به front_of_house
دسترسی داشته باشیم، میتوانیم به hosting
نیز دسترسی داشته باشیم. اما محتویات hosting
همچنان خصوصی است؛ عمومی کردن ماژول به معنای عمومی کردن محتوای آن نیست. کلمه کلیدی pub
روی یک ماژول فقط به کدهای موجود در ماژولهای اجداد اجازه میدهد به آن ارجاع دهند، نه اینکه به کد داخلی آن دسترسی داشته باشند. از آنجایی که ماژولها به عنوان ظرف عمل میکنند، تنها عمومی کردن ماژول کافی نیست؛ باید فراتر رفته و یک یا چند مورد از آیتمهای درون ماژول را نیز عمومی کنیم.
خطاهای موجود در لیستینگ 7-6 نشان میدهند که تابع add_to_waitlist
خصوصی است. قواعد حریم خصوصی برای ساختارها، enumها، توابع، متدها و همچنین ماژولها اعمال میشوند.
بیایید تابع add_to_waitlist
را نیز با اضافه کردن کلمه کلیدی pub
قبل از تعریف آن عمومی کنیم، همانطور که در لیستینگ 7-7 نشان داده شده است.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
pub
به mod hosting
و fn add_to_waitlist
به ما اجازه میدهد تابع را از eat_at_restaurant
فراخوانی کنیمحالا کد کامپایل میشود! برای اینکه ببینیم چرا اضافه کردن کلمه کلیدی pub
به ما اجازه میدهد از این مسیرها در eat_at_restaurant
استفاده کنیم، بیایید به مسیرهای مطلق و نسبی نگاه کنیم.
در مسیر مطلق، با crate
، ریشه درخت ماژول جعبه (crate) خود شروع میکنیم. ماژول front_of_house
در ریشه جعبه (crate) تعریف شده است. اگرچه front_of_house
عمومی نیست، از آنجا که تابع eat_at_restaurant
در همان ماژول به عنوان front_of_house
تعریف شده است (یعنی eat_at_restaurant
و front_of_house
همسطح هستند)، میتوانیم از eat_at_restaurant
به front_of_house
ارجاع دهیم. بعد، ماژول hosting
که با pub
علامتگذاری شده است قرار دارد. ما میتوانیم به ماژول والد hosting
دسترسی داشته باشیم، بنابراین میتوانیم به hosting
دسترسی داشته باشیم. در نهایت، تابع add_to_waitlist
با pub
علامتگذاری شده است و میتوانیم به ماژول والد آن دسترسی داشته باشیم، بنابراین این فراخوانی تابع کار میکند!
در مسیر نسبی، منطق همان مسیر مطلق است با این تفاوت که مرحله اول متفاوت است: به جای شروع از ریشه جعبه (crate)، مسیر از front_of_house
شروع میشود. ماژول front_of_house
در همان ماژولی که eat_at_restaurant
تعریف شده است قرار دارد، بنابراین مسیر نسبی که از ماژولی که eat_at_restaurant
در آن تعریف شده است شروع میشود کار میکند. سپس، از آنجا که hosting
و add_to_waitlist
با pub
علامتگذاری شدهاند، بقیه مسیر کار میکند و این فراخوانی تابع معتبر است!
اگر قصد دارید جعبه (crate) کتابخانه خود را به اشتراک بگذارید تا پروژههای دیگر بتوانند از کد شما استفاده کنند، API عمومی شما قرارداد شما با کاربران جعبه (crate) است که تعیین میکند چگونه میتوانند با کد شما تعامل داشته باشند. نکات زیادی در مورد مدیریت تغییرات API عمومی شما وجود دارد که به افراد کمک میکند به جعبه (crate) شما وابسته باشند. این ملاحظات خارج از دامنه این کتاب هستند؛ اگر به این موضوع علاقهمند هستید، به راهنمای API Rust مراجعه کنید.
بهترین شیوهها برای بستههایی که یک جعبه (crate) باینری و یک جعبه (crate) کتابخانهای دارند
ما اشاره کردیم که یک بسته میتواند هم یک ریشه جعبه (crate) باینری در src/main.rs و هم یک ریشه جعبه (crate) کتابخانهای در src/lib.rs داشته باشد، و هر دو جعبه (crate) به صورت پیشفرض نام بسته را خواهند داشت. معمولاً بستههایی که این الگو را دنبال میکنند فقط به اندازه کافی کد در جعبه (crate) باینری دارند تا یک فایل اجرایی ایجاد کنند که کدی درون جعبه (crate) کتابخانهای را فراخوانی کند. این کار به پروژههای دیگر اجازه میدهد از بیشتر عملکردهایی که بسته ارائه میدهد بهرهمند شوند، زیرا کد جعبه (crate) کتابخانهای میتواند به اشتراک گذاشته شود.
درخت ماژول باید در src/lib.rs تعریف شود. سپس، هر آیتم عمومی را میتوان در جعبه (crate) باینری با شروع مسیرها با نام بسته استفاده کرد. جعبه (crate) باینری به یک کاربر از جعبه (crate) کتابخانهای تبدیل میشود، درست مثل اینکه یک جعبه (crate) کاملاً خارجی از جعبه (crate) کتابخانهای استفاده میکند: تنها میتواند از API عمومی استفاده کند. این کار به شما کمک میکند یک API خوب طراحی کنید؛ نه تنها نویسنده آن هستید، بلکه یک کاربر نیز هستید!
در فصل ۱۲، ما این شیوه سازماندهی را با یک برنامه خط فرمان که هم یک جعبه (crate) باینری و هم یک جعبه (crate) کتابخانهای دارد نشان خواهیم داد.
شروع مسیرهای نسبی با super
ما میتوانیم مسیرهای نسبیای بسازیم که از ماژول والد شروع شوند، نه از ماژول فعلی یا ریشه جعبه (crate)، با استفاده از super
در ابتدای مسیر. این مشابه شروع مسیر در فایلسیستم با سینتکس ..
است. استفاده از super
به ما امکان میدهد به آیتمی که میدانیم در ماژول والد قرار دارد ارجاع دهیم، که میتواند جابجایی درخت ماژول را آسانتر کند، به خصوص زمانی که ماژول به ماژول والد مرتبط است اما ممکن است روزی والد به جای دیگری در درخت ماژول منتقل شود.
کد موجود در لیستینگ 7-8 را در نظر بگیرید که موقعیتی را مدلسازی میکند که در آن یک آشپز سفارش نادرست را اصلاح کرده و شخصاً آن را به مشتری میآورد. تابع fix_incorrect_order
که در ماژول back_of_house
تعریف شده است، تابع deliver_order
را که در ماژول والد تعریف شده است، فراخوانی میکند و مسیر deliver_order
را با شروع از super
مشخص میکند.
fn deliver_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::deliver_order();
}
fn cook_order() {}
}
super
شروع میشودتابع fix_incorrect_order
در ماژول back_of_house
است، بنابراین میتوانیم از super
برای رفتن به ماژول والد back_of_house
استفاده کنیم، که در این مورد crate
، یعنی ریشه است. از آنجا به دنبال deliver_order
میگردیم و آن را پیدا میکنیم. موفقیت! ما فکر میکنیم که ماژول back_of_house
و تابع deliver_order
احتمالاً در همان رابطه با یکدیگر باقی میمانند و اگر بخواهیم درخت ماژول جعبه (crate) را سازماندهی مجدد کنیم، با هم جابجا میشوند. بنابراین، از super
استفاده کردیم تا در آینده، اگر این کد به ماژول دیگری منتقل شد، تغییرات کمتری در کد لازم باشد.
عمومی کردن ساختارها و enumها
ما همچنین میتوانیم از pub
برای مشخص کردن ساختارها و enumها به عنوان عمومی استفاده کنیم، اما چند جزئیات اضافی در مورد استفاده از pub
با ساختارها و enumها وجود دارد. اگر از pub
قبل از تعریف یک ساختار استفاده کنیم، ساختار عمومی میشود، اما فیلدهای ساختار همچنان خصوصی خواهند بود. ما میتوانیم هر فیلد را به صورت موردی عمومی یا خصوصی کنیم. در لیستینگ 7-9، یک ساختار عمومی به نام back_of_house::Breakfast
تعریف کردهایم که یک فیلد عمومی به نام toast
دارد اما فیلد seasonal_fruit
خصوصی است. این مدلسازی حالتی است که در آن مشتری میتواند نوع نان همراه با وعده غذایی را انتخاب کند، اما سرآشپز تصمیم میگیرد که کدام میوه همراه وعده غذایی باشد بر اساس آنچه در فصل و موجودی است. میوههای موجود به سرعت تغییر میکنند، بنابراین مشتریان نمیتوانند میوه را انتخاب کنند یا حتی ببینند که چه میوهای دریافت خواهند کرد.
mod back_of_house {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("peaches"),
}
}
}
}
pub fn eat_at_restaurant() {
// Order a breakfast in the summer with Rye toast
let mut meal = back_of_house::Breakfast::summer("Rye");
// Change our mind about what bread we'd like
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
// The next line won't compile if we uncomment it; we're not allowed
// to see or modify the seasonal fruit that comes with the meal
// meal.seasonal_fruit = String::from("blueberries");
}
از آنجا که فیلد toast
در ساختار back_of_house::Breakfast
عمومی است، میتوانیم در eat_at_restaurant
به این فیلد با استفاده از نقطهگذاری مقدار بدهیم یا مقدار آن را بخوانیم. توجه کنید که نمیتوانیم از فیلد seasonal_fruit
در eat_at_restaurant
استفاده کنیم، زیرا seasonal_fruit
خصوصی است. خطی که مقدار فیلد seasonal_fruit
را تغییر میدهد را لغو کامنت کنید تا ببینید چه خطایی دریافت میکنید!
همچنین توجه کنید که چون back_of_house::Breakfast
یک فیلد خصوصی دارد، ساختار باید یک تابع وابسته عمومی ارائه دهد که یک نمونه از Breakfast
بسازد (ما آن را اینجا summer
نامیدهایم). اگر Breakfast
چنین تابعی نداشت، نمیتوانستیم یک نمونه از Breakfast
را در eat_at_restaurant
ایجاد کنیم، زیرا نمیتوانستیم مقدار فیلد خصوصی seasonal_fruit
را در eat_at_restaurant
تنظیم کنیم.
در مقابل، اگر یک enum را عمومی کنیم، تمام متغیرهای آن نیز عمومی میشوند. ما فقط به pub
قبل از کلمه کلیدی enum
نیاز داریم، همانطور که در لیستینگ 7-10 نشان داده شده است.
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
pub fn eat_at_restaurant() {
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
از آنجایی که enum Appetizer
را عمومی کردیم، میتوانیم از متغیرهای Soup
و Salad
در eat_at_restaurant
استفاده کنیم.
Enums خیلی مفید نیستند مگر اینکه متغیرهای آنها عمومی باشند؛ اضافه کردن pub
به تمام متغیرهای enum در هر مورد کار خستهکنندهای خواهد بود، بنابراین به طور پیشفرض متغیرهای enum عمومی هستند. ساختارها اغلب بدون عمومی بودن فیلدهایشان مفید هستند، بنابراین فیلدهای ساختار از قانون کلی پیروی میکنند که همه چیز به صورت پیشفرض خصوصی است مگر اینکه با pub
مشخص شود.
یک وضعیت دیگر مرتبط با pub
وجود دارد که هنوز آن را پوشش ندادهایم، و آن آخرین ویژگی سیستم ماژول ما است: کلمه کلیدی use
. ابتدا use
را به تنهایی بررسی خواهیم کرد، و سپس نشان خواهیم داد چگونه pub
و use
را ترکیب کنیم.