وارد کردن مسیرها به محدوده با کلمه کلیدی 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 داشته باشیم.

Filename: src/lib.rs
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();
}
Listing 7-11: وارد کردن یک ماژول به محدوده با use

اضافه کردن use و یک مسیر در یک محدوده مشابه ایجاد یک لینک نمادین در فایل‌سیستم است. با اضافه کردن use crate::front_of_house::hosting در ریشه جعبه (crate)، hosting اکنون یک نام معتبر در آن محدوده است، درست مانند اینکه ماژول hosting در ریشه جعبه (crate) تعریف شده باشد. مسیرهایی که با use به محدوده آورده می‌شوند مانند هر مسیر دیگری حریم خصوصی را بررسی می‌کنند.

توجه کنید که use فقط میانبر را برای محدوده خاصی که در آن use استفاده شده ایجاد می‌کند. لیستینگ 7-12 تابع eat_at_restaurant را به یک زیرماژول جدید به نام customer منتقل می‌کند که سپس یک محدوده متفاوت از دستور use است، بنابراین بدنه تابع کامپایل نمی‌شود.

Filename: src/lib.rs
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();
    }
}
Listing 7-12: یک دستور 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 نشان داده شده است.

Filename: src/lib.rs
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();
}
Listing 7-13: وارد کردن تابع add_to_waitlist به محدوده با use که غیر ایدیوماتیک است

اگرچه هم لیستینگ 7-11 و هم لیستینگ 7-13 کار مشابهی انجام می‌دهند، لیستینگ 7-11 روش ایدیوماتیک برای وارد کردن یک تابع به محدوده با use است. وارد کردن ماژول والد تابع با use به این معنا است که باید ماژول والد را هنگام فراخوانی تابع مشخص کنیم. مشخص کردن ماژول والد هنگام فراخوانی تابع نشان می‌دهد که تابع به صورت محلی تعریف نشده است، در حالی که همچنان تکرار مسیر کامل را به حداقل می‌رساند. کد موجود در لیستینگ 7-13 مشخص نمی‌کند که add_to_waitlist کجا تعریف شده است.

از طرف دیگر، وقتی ساختارها، enumها، و سایر آیتم‌ها را با use وارد می‌کنیم، ایدیوماتیک است که مسیر کامل را مشخص کنیم. لیستینگ 7-14 روش ایدیوماتیک برای وارد کردن ساختار HashMap از کتابخانه استاندارد به محدوده جعبه (crate) باینری را نشان می‌دهد.

Filename: src/main.rs
use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}
Listing 7-14: وارد کردن HashMap به محدوده به روش ایدیوماتیک

هیچ دلیل قوی پشت این عرف نیست: این فقط کنوانسیونی است که در جامعه Rust به وجود آمده و افراد به خواندن و نوشتن کد Rust به این روش عادت کرده‌اند.

استثنای این عرف زمانی است که دو آیتم با نام یکسان را با دستورات use وارد محدوده می‌کنیم، زیرا Rust این اجازه را نمی‌دهد. لیستینگ 7-15 نشان می‌دهد که چگونه دو نوع Result را که نام یکسانی دارند اما از ماژول‌های والد متفاوتی می‌آیند وارد محدوده کنیم و چگونه به آن‌ها ارجاع دهیم.

Filename: src/lib.rs
use std::fmt;
use std::io;

fn function1() -> fmt::Result {
    // --snip--
    Ok(())
}

fn function2() -> io::Result<()> {
    // --snip--
    Ok(())
}
Listing 7-15: وارد کردن دو نوع با نام یکسان به یک محدوده نیازمند استفاده از ماژول‌های والد آن‌ها است.

همان‌طور که می‌بینید، استفاده از ماژول‌های والد دو نوع Result را از هم متمایز می‌کند. اگر به جای آن use std::fmt::Result و use std::io::Result مشخص کنیم، دو نوع Result در یک محدوده خواهیم داشت و Rust نمی‌تواند بفهمد منظور ما از Result کدام است.

ارائه نام‌های جدید با کلمه کلیدی as

یک راه‌حل دیگر برای مشکل وارد کردن دو نوع با نام یکسان به یک محدوده با use این است که پس از مسیر، با استفاده از as یک نام محلی جدید یا نام مستعار برای نوع مشخص کنیم. لیستینگ 7-16 راه دیگری برای نوشتن کد در لیستینگ 7-15 را نشان می‌دهد که در آن یکی از دو نوع Result را با استفاده از as تغییر نام داده‌ایم.

Filename: src/lib.rs
use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    // --snip--
    Ok(())
}

fn function2() -> IoResult<()> {
    // --snip--
    Ok(())
}
Listing 7-16: تغییر نام یک نوع هنگام وارد کردن به محدوده با کلمه کلیدی 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 نشان می‌دهد.

Filename: src/lib.rs
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();
}
Listing 7-17: در دسترس قرار دادن یک نام برای هر کدی که از محدوده جدید استفاده می‌کند با 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 اضافه کردیم:

Filename: 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 را به محدوده می‌آورند:

Filename: src/main.rs
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 نشان داده شده است.

Filename: src/main.rs
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!"),
    }
}
Listing 7-18: مشخص کردن یک مسیر تو در تو برای وارد کردن چندین آیتم با پیشوند مشابه به محدوده

در برنامه‌های بزرگ‌تر، وارد کردن بسیاری از آیتم‌ها از یک جعبه (crate) یا ماژول مشابه با استفاده از مسیرهای تو در تو می‌تواند تعداد دستورات use جداگانه مورد نیاز را به طور قابل‌توجهی کاهش دهد.

ما می‌توانیم در هر سطحی از یک مسیر، از یک مسیر تو در تو استفاده کنیم، که این کار در مواقعی که دو دستور use دارای یک زیرمسیر مشترک هستند، مفید است. برای مثال، لیستینگ 7-19 دو دستور use را نشان می‌دهد: یکی که std::io را به محدوده وارد می‌کند و دیگری که std::io::Write را به محدوده وارد می‌کند.

Filename: src/lib.rs
use std::io;
use std::io::Write;
Listing 7-19: دو دستور use که یکی زیرمسیر دیگری است

بخش مشترک این دو مسیر، std::io است که مسیر کامل اولین دستور use را تشکیل می‌دهد. برای ترکیب این دو مسیر به یک دستور use، می‌توانیم از self در مسیر تو در تو استفاده کنیم، همان‌طور که در لیستینگ 7-20 نشان داده شده است.

Filename: src/lib.rs
use std::io::{self, Write};
Listing 7-20: ترکیب مسیرهای موجود در لیستینگ 7-19 به یک دستور use

این خط، std::io و std::io::Write را به محدوده وارد می‌کند.

عملگر Glob

اگر بخواهیم تمام آیتم‌های عمومی تعریف‌شده در یک مسیر را به محدوده وارد کنیم، می‌توانیم آن مسیر را به همراه عملگر * مشخص کنیم:

#![allow(unused)]
fn main() {
use std::collections::*;
}

این دستور use تمام آیتم‌های عمومی تعریف‌شده در std::collections را به محدوده فعلی وارد می‌کند. هنگام استفاده از عملگر glob دقت کنید! استفاده از glob می‌تواند تشخیص این که چه نام‌هایی در محدوده قرار دارند و نامی که در برنامه شما استفاده شده در کجا تعریف شده است را دشوارتر کند.

عملگر glob اغلب در زمان تست استفاده می‌شود تا همه چیز تحت تست به ماژول tests وارد شود؛ در بخش «چگونه تست بنویسیم» در فصل 11 در مورد این موضوع صحبت خواهیم کرد. عملگر glob همچنین گاهی به عنوان بخشی از الگوی prelude استفاده می‌شود: برای اطلاعات بیشتر در مورد این الگو، به مستندات کتابخانه استاندارد مراجعه کنید.