Bringing Paths into Scope with the use Keyword

雖然我們可以使用絕對路徑來呼叫項目,但如果每一次呼叫都寫一長串路徑,會讓程式碼顯得有些冗長。

我們可以使用 use 關鍵字將模組引入作用域,之後若想使用模組,就不需要寫完整的路徑。

如下方的範例,如果想使用 add_to_waitlist,我們可以使用 use 先將 hosting 模組引入作用域中。

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

// 使用 use 將 hosting 模組帶入作用域
use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    // 這時候想使用 add_to_waitlist 就不需要大費周章再寫一次完整的路徑
    hosting::add_to_waitlist();
}

需要注意的是,use 只會引入當前放置的作用域。

在下方的例子中,如果我們將 eat_at_restaurant 移動到 custom 模組下, 就會因為作用域不相同,導致在呼叫 add_to_waitlist 時發生編譯錯誤。

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

// 如果 use 之後沒有使用也會產生編譯錯誤
// warning: unused import: `crate::front_of_house::hosting`
use crate::front_of_house::hosting;

mod customer {
    // 這裡的作用域與 use crate::front_of_house::hosting; 並不相同
    pub fn eat_at_restaurant() {
        // error: failed to resolve: use of undeclared crate or module `hosting`
        hosting::add_to_waitlist();
    }
}

雖然我們可以使用完整路徑將函式引入,不過引入模組是比較推崇的做法,因為我們可以清楚的知道該函式是定義在哪個模組之下。

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();
}

另一方面,如果是 structs、enums 與其它項目,比較推崇的做法是引入完整路徑

例如下方使用 Rust 標準函式庫 HashMap struct 的例子。

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}

這個做法並沒有很強硬的理由,比較像是約定成俗的共識。

引入相同名稱的項目

雖然剛剛說項目類型在引用時會盡量使用完整路徑,但有個例外。

如果你想引入兩個名稱一模一樣的項目,引入時都寫完整名稱就會發生編譯錯誤。

因為後續項目的使用,編譯器無法知道你想使用哪一個項目 (即使是人也看不太出來)。

use std::fmt::Result;
use std::io::Result;

// 這是哪一個 Result?
fn function1() -> Result {
    // --snip--
    Ok(())
}

這時候可以引入上一層,也就是項目的模組名稱,來避免編譯錯誤。

// error
// use std::fmt::Result;
// use std::io::Result;

use std::fmt;
use std::io;

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

fn function2() -> io::Result<()> {
    // --snip--
    Ok(())
}

除了上述這種方式,Rust 還提供一種使用別名 as 的方式來處理引入相同名稱項目的問題。

use std::fmt::Result;
use std::io::Result as IoResult;

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

fn function2() -> IoResult<()> {
    // --snip--
    Ok(())
}

這兩種方式沒有誰好誰壞,端看開發者的選擇。

通過 pub use 重導出 (Re-exporting) 名稱

當使用 use 將名稱導入作用域時,在新的作用域中會是私有的。

如果你希望導入的名稱在新的作用域是公有的,可以使用 pub use

mod sound {
    pub mod instrument {
        pub fn clarinet() {
            // ...
        }
    }
}

mod performance_group {
    // 如果沒有使用 pub use,那麼這裡 performance_group::instrument 就會是私有的
    pub use crate::sound::instrument;

    pub fn clarinet_trio() {
        instrument::clarinet();
        instrument::clarinet();
        instrument::clarinet();
    }
}

fn main() {
    performance_group::clarinet_trio();
    // 使用 pub use 讓引入的項目變為公有,使外部可以接續使用
    performance_group::instrument::clarinet();
}

使用外部套件

我們可以使用 rand 這個外部套件來產生一組隨機數。

如果想使用 rand 這個外部套件,我們需要在 cargo.toml 加上一行。

rand = "0.8.5"

此時 Cargo 就會知道我們的專案相依於 rand 這個外部套件,並幫我們下載這個套件。

接下來,如果想在程式碼中使用這個套件,還需要使用 use 將這個套件引入作用域中。

use rand::Rng;

fn main() {
    let secret_number = rand::thread_rng().gen_range(1..=100);

    // ...
}

Rust 社群開發了許多實用的套件並放在 crates.io 上。

Rust 標準函式庫中的 std 雖然也被視為外部套件,但因為已經包含在 Rust 中,所以我們不需要在 cargo.toml 中額外列出並下載。

使用嵌套路徑整理大量的 use 列表

如果我們需要使用很多外部套件,可能會有一整排多到數不清的 use 出現在你的程式碼中。

// ...
use std::cmp::Ordering;
use std::io;

這時我們可以使用嵌套路徑的方式,將有著相同前綴 (prefix) 底下的名稱整理在一起。

use std::{cmp::Ordering, io};

另外一個範例。

// before
use std::io;
use std::io::Write;

// after
use std::io::{self, Write};

Glob 運算符

如果我們想要將一個名稱底下所有公有項目引入作用域中,可以使用 *

use std::collections::*;

這會將 std::collections 底下所有公有項目引入當前的作用域中。

但要注意的是,使用 glob 會讓開發者難以得知引入的項目是定義在何處。

通常使用 glob 是在撰寫測試時,我們會將 tests 模組所有方法都引入作用域中,方便撰寫測試。


This site uses Just the Docs, a documentation theme for Jekyll.