مسیرها برای اشاره به یک آیتم در درخت ماژول
برای نشان دادن به 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() {}
}
}
// -- snip --
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:10:37
|
10 | 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:13:30
|
13 | 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() {}
}
}
// -- snip --
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
فراخوانی کنیمNow the code will compile! To see why adding the pub
keyword lets us use
these paths in eat_at_restaurant
with respect to the privacy rules, let’s look
at the absolute and the relative paths.
In the absolute path, we start with crate
, the root of our crate’s module
tree. The front_of_house
module is defined in the crate root. While
front_of_house
isn’t public, because the eat_at_restaurant
function is
defined in the same module as front_of_house
(that is, eat_at_restaurant
and front_of_house
are siblings), we can refer to front_of_house
from
eat_at_restaurant
. Next is the hosting
module marked with pub
. We can
access the parent module of hosting
, so we can access hosting
. Finally, the
add_to_waitlist
function is marked with pub
and we can access its parent
module, so this function call works!
In the relative path, the logic is the same as the absolute path except for the
first step: rather than starting from the crate root, the path starts from
front_of_house
. The front_of_house
module is defined within the same module
as eat_at_restaurant
, so the relative path starting from the module in which
eat_at_restaurant
is defined works. Then, because hosting
and
add_to_waitlist
are marked with pub
, the rest of the path works, and this
function call is valid!
If you plan on sharing your library crate so other projects can use your code, your public API is your contract with users of your crate that determines how they can interact with your code. There are many considerations around managing changes to your public API to make it easier for people to depend on your crate. These considerations are out of the scope of this book; if you’re interested in this topic, see The Rust API Guidelines.
بهترین شیوهها برای بستههایی که یک جعبه (crate) باینری و یک جعبه (crate) کتابخانهای دارند
We mentioned that a package can contain both a src/main.rs binary crate root as well as a src/lib.rs library crate root, and both crates will have the package name by default. Typically, packages with this pattern of containing both a library and a binary crate will have just enough code in the binary crate to start an executable that calls code within the library crate. This lets other projects benefit from most of the functionality that the package provides because the library crate’s code can be shared.
درخت ماژول باید در src/lib.rs تعریف شود. سپس، هر آیتم عمومی را میتوان در جعبه (crate) باینری با شروع مسیرها با نام بسته استفاده کرد. جعبه (crate) باینری به یک کاربر از جعبه (crate) کتابخانهای تبدیل میشود، درست مثل اینکه یک جعبه (crate) کاملاً خارجی از جعبه (crate) کتابخانهای استفاده میکند: تنها میتواند از API عمومی استفاده کند. این کار به شما کمک میکند یک API خوب طراحی کنید؛ نه تنها نویسنده آن هستید، بلکه یک کاربر نیز هستید!
In Chapter 12, we’ll demonstrate this organizational practice with a command-line program that will contain both a binary crate and a library crate.
Starting Relative Paths with super
We can construct relative paths that begin in the parent module, rather than
the current module or the crate root, by using super
at the start of the
path. This is like starting a filesystem path with the ..
syntax. Using
super
allows us to reference an item that we know is in the parent module,
which can make rearranging the module tree easier when the module is closely
related to the parent but the parent might be moved elsewhere in the module
tree someday.
Consider the code in Listing 7-8 that models the situation in which a chef
fixes an incorrect order and personally brings it out to the customer. The
function fix_incorrect_order
defined in the back_of_house
module calls the
function deliver_order
defined in the parent module by specifying the path to
deliver_order
, starting with 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
را ترکیب کنیم.