وارد کردن مسیرها به محدوده با کلمه کلیدی use
نوشتن مسیرهای کامل برای فراخوانی توابع میتواند خستهکننده و تکراری باشد. در لیستینگ 7-7، چه مسیر مطلق یا نسبی را برای تابع add_to_waitlist
انتخاب کنیم، هر بار که بخواهیم این تابع را فراخوانی کنیم باید front_of_house
و hosting
را نیز مشخص کنیم. خوشبختانه، راهی برای سادهتر کردن این فرآیند وجود دارد: میتوانیم یک میانبر به یک مسیر با استفاده از کلمه کلیدی use
ایجاد کنیم و سپس در هر جای دیگر محدوده، از نام کوتاهتر استفاده کنیم.
در لیستینگ 7-11، ماژول crate::front_of_house::hosting
را به محدوده تابع eat_at_restaurant
میآوریم تا فقط نیاز به مشخص کردن hosting::add_to_waitlist
برای فراخوانی تابع add_to_waitlist
در eat_at_restaurant
داشته باشیم.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
use
اضافه کردن use
و یک مسیر در یک محدوده مشابه ایجاد یک لینک نمادین در فایلسیستم است. با اضافه کردن use crate::front_of_house::hosting
در ریشه جعبه (crate)، hosting
اکنون یک نام معتبر در آن محدوده است، درست مانند اینکه ماژول hosting
در ریشه جعبه (crate) تعریف شده باشد. مسیرهایی که با use
به محدوده آورده میشوند مانند هر مسیر دیگری حریم خصوصی را بررسی میکنند.
توجه کنید که use
فقط میانبر را برای محدوده خاصی که در آن use
استفاده شده ایجاد میکند. لیستینگ 7-12 تابع eat_at_restaurant
را به یک زیرماژول جدید به نام customer
منتقل میکند که سپس یک محدوده متفاوت از دستور use
است، بنابراین بدنه تابع کامپایل نمیشود.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
mod customer {
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
}
use
فقط در محدودهای که در آن قرار دارد اعمال میشودخطای کامپایلر نشان میدهد که میانبر دیگر در ماژول customer
اعمال نمیشود:
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0433]: failed to resolve: use of undeclared crate or module `hosting`
--> src/lib.rs:11:9
|
11 | hosting::add_to_waitlist();
| ^^^^^^^ use of undeclared crate or module `hosting`
|
help: consider importing this module through its public re-export
|
10 + use crate::hosting;
|
warning: unused import: `crate::front_of_house::hosting`
--> src/lib.rs:7:5
|
7 | use crate::front_of_house::hosting;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
For more information about this error, try `rustc --explain E0433`.
warning: `restaurant` (lib) generated 1 warning
error: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted
توجه کنید که همچنین یک هشدار وجود دارد که use
دیگر در محدوده خود استفاده نمیشود! برای رفع این مشکل، دستور use
را نیز به داخل ماژول customer
منتقل کنید، یا میانبر را در ماژول والد با super::hosting
در داخل ماژول customer
ارجاع دهید.
ایجاد مسیرهای use
به صورت ایدیوماتیک
در لیستینگ 7-11، ممکن است این سوال پیش بیاید که چرا ما use crate::front_of_house::hosting
را مشخص کردهایم و سپس hosting::add_to_waitlist
را در eat_at_restaurant
فراخوانی کردهایم، به جای اینکه مسیر use
را تا تابع add_to_waitlist
مشخص کنیم تا همان نتیجه را به دست آوریم، همانطور که در لیستینگ 7-13 نشان داده شده است.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting::add_to_waitlist;
pub fn eat_at_restaurant() {
add_to_waitlist();
}
add_to_waitlist
به محدوده با use
که غیر ایدیوماتیک استاگرچه هم لیستینگ 7-11 و هم لیستینگ 7-13 کار مشابهی انجام میدهند، لیستینگ 7-11 روش ایدیوماتیک برای وارد کردن یک تابع به محدوده با use
است. وارد کردن ماژول والد تابع با use
به این معنا است که باید ماژول والد را هنگام فراخوانی تابع مشخص کنیم. مشخص کردن ماژول والد هنگام فراخوانی تابع نشان میدهد که تابع به صورت محلی تعریف نشده است، در حالی که همچنان تکرار مسیر کامل را به حداقل میرساند. کد موجود در لیستینگ 7-13 مشخص نمیکند که add_to_waitlist
کجا تعریف شده است.
از طرف دیگر، وقتی ساختارها، enumها، و سایر آیتمها را با use
وارد میکنیم، ایدیوماتیک است که مسیر کامل را مشخص کنیم. لیستینگ 7-14 روش ایدیوماتیک برای وارد کردن ساختار HashMap
از کتابخانه استاندارد به محدوده جعبه (crate) باینری را نشان میدهد.
use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(1, 2); }
HashMap
به محدوده به روش ایدیوماتیکهیچ دلیل قوی پشت این عرف نیست: این فقط کنوانسیونی است که در جامعه Rust به وجود آمده و افراد به خواندن و نوشتن کد Rust به این روش عادت کردهاند.
استثنای این عرف زمانی است که دو آیتم با نام یکسان را با دستورات use
وارد محدوده میکنیم، زیرا Rust این اجازه را نمیدهد. لیستینگ 7-15 نشان میدهد که چگونه دو نوع Result
را که نام یکسانی دارند اما از ماژولهای والد متفاوتی میآیند وارد محدوده کنیم و چگونه به آنها ارجاع دهیم.
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
Ok(())
}
fn function2() -> io::Result<()> {
// --snip--
Ok(())
}
همانطور که میبینید، استفاده از ماژولهای والد دو نوع Result
را از هم متمایز میکند. اگر به جای آن use std::fmt::Result
و use std::io::Result
مشخص کنیم، دو نوع Result
در یک محدوده خواهیم داشت و Rust نمیتواند بفهمد منظور ما از Result
کدام است.
ارائه نامهای جدید با کلمه کلیدی as
یک راهحل دیگر برای مشکل وارد کردن دو نوع با نام یکسان به یک محدوده با use
این است که پس از مسیر، با استفاده از as
یک نام محلی جدید یا نام مستعار برای نوع مشخص کنیم. لیستینگ 7-16 راه دیگری برای نوشتن کد در لیستینگ 7-15 را نشان میدهد که در آن یکی از دو نوع Result
را با استفاده از as
تغییر نام دادهایم.
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
Ok(())
}
fn function2() -> IoResult<()> {
// --snip--
Ok(())
}
as
در دستور دوم use
، ما نام جدید IoResult
را برای نوع std::io::Result
انتخاب کردیم، که با نوع Result
از std::fmt
که آن را نیز وارد محدوده کردهایم، تضاد نخواهد داشت. هر دو لیستینگ 7-15 و 7-16 ایدیوماتیک در نظر گرفته میشوند، بنابراین انتخاب با شماست!
دوباره صادر کردن نامها با pub use
وقتی با استفاده از کلمه کلیدی use
یک نام را وارد محدوده میکنیم، نام وارد شده در محدوده جدید خصوصی است. برای این که کدی که کد ما را فراخوانی میکند بتواند به این نام به گونهای ارجاع دهد که گویی در محدوده کد خود تعریف شده است، میتوانیم pub
و use
را ترکیب کنیم. این تکنیک دوباره صادر کردن نامیده میشود زیرا ما یک آیتم را وارد محدوده میکنیم و همچنین آن را برای دیگران در دسترس قرار میدهیم تا وارد محدوده خودشان کنند.
لیستینگ 7-17 کد موجود در لیستینگ 7-11 را با تغییر دستور use
در ماژول ریشه به pub use
نشان میدهد.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
pub use
قبل از این تغییر، کد خارجی باید تابع add_to_waitlist
را با استفاده از مسیر restaurant::front_of_house::hosting::add_to_waitlist()
فراخوانی میکرد، که همچنین نیاز داشت ماژول front_of_house
به عنوان pub
علامتگذاری شود. حالا که این pub use
ماژول hosting
را از ماژول ریشه دوباره صادر کرده است، کد خارجی میتواند از مسیر restaurant::hosting::add_to_waitlist()
استفاده کند.
دوباره صادر کردن زمانی مفید است که ساختار داخلی کد شما با نحوه فکر کردن برنامهنویسانی که کد شما را فراخوانی میکنند در مورد دامنه متفاوت باشد. برای مثال، در این استعاره از رستوران، افرادی که رستوران را مدیریت میکنند در مورد “جلوی خانه” و “پشت خانه” فکر میکنند. اما مشتریانی که به رستوران میآیند احتمالاً در این قالب به بخشهای رستوران فکر نمیکنند. با استفاده از pub use
، میتوانیم کد خود را با یک ساختار بنویسیم اما یک ساختار متفاوت را آشکار کنیم. این کار کتابخانه ما را برای برنامهنویسانی که روی آن کار میکنند و همچنین برای برنامهنویسانی که از آن استفاده میکنند، خوب سازماندهی میکند. در بخش «صادرات یک API عمومی مناسب با pub use
» فصل ۱۴ به مثال دیگری از pub use
و تأثیر آن بر مستندات جعبه (crate) شما خواهیم پرداخت.
استفاده از بستههای خارجی
در فصل ۲، ما یک پروژه بازی حدسزنی برنامهریزی کردیم که از یک بسته خارجی به نام rand
برای تولید اعداد تصادفی استفاده میکرد. برای استفاده از rand
در پروژه خود، این خط را به Cargo.toml اضافه کردیم:
rand = "0.8.5"
اضافه کردن rand
به عنوان یک وابستگی در Cargo.toml به Cargo میگوید که بسته rand
و هرگونه وابستگی را از crates.io دانلود کرده و rand
را در پروژه ما در دسترس قرار دهد.
سپس، برای وارد کردن تعاریف rand
به محدوده بسته خود، یک خط use
اضافه کردیم که با نام جعبه (crate)، rand
شروع میشد و آیتمهایی را که میخواستیم وارد محدوده کنیم فهرست کردیم. به یاد بیاورید که در بخش «تولید یک عدد تصادفی» فصل ۲، ما ویژگی Rng
را به محدوده آوردیم و تابع rand::thread_rng
را فراخوانی کردیم:
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
}
اعضای جامعه Rust بسیاری از بستهها را در crates.io به اشتراک گذاشتهاند، و وارد کردن هر یک از آنها به بسته شما شامل این مراحل است: فهرست کردن آنها در فایل Cargo.toml بسته شما و استفاده از use
برای وارد کردن آیتمها از جعبه (crate) آنها به محدوده.
توجه داشته باشید که کتابخانه استاندارد std
نیز یک جعبه (crate) خارجی برای بسته ما است. از آنجا که کتابخانه استاندارد همراه با زبان Rust ارائه میشود، نیازی به تغییر Cargo.toml برای گنجاندن std
نداریم. اما برای وارد کردن آیتمها از آن به محدوده بسته خود، باید به آن با use
ارجاع دهیم. برای مثال، با HashMap
از این خط استفاده میکردیم:
#![allow(unused)] fn main() { use std::collections::HashMap; }
این یک مسیر مطلق است که با std
، نام جعبه (crate) کتابخانه استاندارد، شروع میشود.
استفاده از مسیرهای تو در تو برای سادهسازی لیستهای بزرگ use
اگر از چندین آیتم تعریفشده در یک جعبه (crate) یا ماژول استفاده کنیم، فهرست کردن هر آیتم در خط خود میتواند فضای عمودی زیادی در فایلهای ما اشغال کند. برای مثال، این دو دستور use
که در بازی حدسزنی در لیستینگ ۲-۴ استفاده کردیم آیتمهایی از std
را به محدوده میآورند:
use rand::Rng;
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
در عوض، میتوانیم از مسیرهای تو در تو استفاده کنیم تا همان آیتمها را در یک خط به محدوده بیاوریم. این کار را با مشخص کردن بخش مشترک مسیر، به دنبال آن دو نقطه دوبل و سپس یک لیست از بخشهای متفاوت مسیرها در داخل آکولاد انجام میدهیم، همانطور که در لیستینگ 7-18 نشان داده شده است.
use rand::Rng;
// --snip--
use std::{cmp::Ordering, io};
// --snip--
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = guess.trim().parse().expect("Please type a number!");
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
در برنامههای بزرگتر، وارد کردن بسیاری از آیتمها از یک جعبه (crate) یا ماژول مشابه با استفاده از مسیرهای تو در تو میتواند تعداد دستورات use
جداگانه مورد نیاز را به طور قابلتوجهی کاهش دهد.
ما میتوانیم در هر سطحی از یک مسیر، از یک مسیر تو در تو استفاده کنیم، که این کار در مواقعی که دو دستور use
دارای یک زیرمسیر مشترک هستند، مفید است. برای مثال، لیستینگ 7-19 دو دستور use
را نشان میدهد: یکی که std::io
را به محدوده وارد میکند و دیگری که std::io::Write
را به محدوده وارد میکند.
use std::io;
use std::io::Write;
use
که یکی زیرمسیر دیگری استبخش مشترک این دو مسیر، std::io
است که مسیر کامل اولین دستور use
را تشکیل میدهد. برای ترکیب این دو مسیر به یک دستور use
، میتوانیم از self
در مسیر تو در تو استفاده کنیم، همانطور که در لیستینگ 7-20 نشان داده شده است.
use std::io::{self, Write};
use
این خط، std::io
و std::io::Write
را به محدوده وارد میکند.
عملگر Glob
اگر بخواهیم تمام آیتمهای عمومی تعریفشده در یک مسیر را به محدوده وارد کنیم، میتوانیم آن مسیر را به همراه عملگر *
مشخص کنیم:
#![allow(unused)] fn main() { use std::collections::*; }
این دستور use
تمام آیتمهای عمومی تعریفشده در std::collections
را به محدوده فعلی وارد میکند. هنگام استفاده از عملگر glob دقت کنید! استفاده از glob میتواند تشخیص این که چه نامهایی در محدوده قرار دارند و نامی که در برنامه شما استفاده شده در کجا تعریف شده است را دشوارتر کند.
عملگر glob اغلب در زمان تست استفاده میشود تا همه چیز تحت تست به ماژول tests
وارد شود؛ در بخش «چگونه تست بنویسیم» در فصل 11 در مورد این موضوع صحبت خواهیم کرد. عملگر glob همچنین گاهی به عنوان بخشی از الگوی prelude استفاده میشود: برای اطلاعات بیشتر در مورد این الگو، به مستندات کتابخانه استاندارد مراجعه کنید.