箱和模塊

2018-08-12 22:03 更新

箱和模塊

當一個項目開始變大時,我們通常認為的良好的軟件工程實踐是把它分割成小塊,然后把它們組合在一起。同樣重要的是有一個定義良好的接口,這樣你的一些功能可以是私人的,另外一些可以是公開的。為了促進這些事情,Rust 使用了模塊系統(tǒng)。

基本術語:箱和模塊

關與模塊系統(tǒng),Rust 有兩個不同的術語:“箱” 和“模塊”。 在其他語言里箱的代名詞是 “庫” 或 “包”。因此 “Cargo” 就是 Rust語言的的包管理工具:你可以用Cargo 裝載你的箱轉運給其他程序。根據(jù)不同的項目,箱可以產生一個可執(zhí)行文件或庫。

每個箱有一個包含箱代碼的隱式根模塊。然后,您可以定義一個根模塊下的子樹模塊。 模塊允許你分區(qū)箱內箱外代碼。

作為一個例子,讓我們做一個短語箱,它將在不同的語言中給我們不同的詞語。為簡單起見,我們將使用 “問候” 和 “告別” 兩種類型的短語,并使用英語和日語 (日本語) 兩種語言。我們將使用下面這個模塊布局:

image

在這個例子中,短語是我們箱的名字。其余都是模塊。你可以看到,他們形成一個樹,分支從箱根發(fā)出,根指的是樹的根:短語本身。

現(xiàn)在我們有一個計劃,讓我們來在代碼中定義這些模塊。首先,用 Cargo 生成一個新的箱:

    $ cargo new phrases
    $ cd phrases

如果你記得以前所講的,這將為我們生成一個簡單的項目:

    $ tree .
    .
    ├── Cargo.toml
    └── src
    └── lib.rs

    1 directory, 2 files

src/lib.rs 是我們箱根,對應于我們在上圖中的短語。

定義模塊

我們使用 mod 關鍵字來定義我們的每個模塊。讓我們使我們的 src/lib.rs,看起來就像這樣:

    mod english {
    mod greetings {
    }

    mod farewells {
    }
    }

    mod japanese {
    mod greetings {
    }

    mod farewells {
    }
    }

在 mod 關鍵字后,我們給出模塊的名稱。模塊名稱遵守 Rus t規(guī)定的標識符命名規(guī)則:lower_snake_case。每個模塊的內容在花括號 ({ }) 里面。

在一個給定的模式下,您可以聲明 sub-mods。我們可以用雙冒號 (::) 符號引用子模塊:我們的四個嵌套模塊是 english::greetings, english::farewells, japanese::greetings, 還有 japanese::farewells。因為這些子模塊是在他們父模塊命名空間命名的,名字不沖突: english::greetings 和 japanese::greetings 是不同的,盡管他們的名字都是問候。

因為這個箱子沒有 main() 函數(shù),并且被稱為 lib.rs, Cargo 將把這個箱建成一個庫:

    $ cargo build
       Compiling phrases v0.0.1 (file:///home/you/projects/phrases)
    $ ls target/debug
    build  deps  examples  libphrases-a7448e02a0468eaa.rlib  native

libphrase-hash.rlib 是編譯后的箱。在我們知道如何在另一個箱里面使用這個箱之前,讓我們把它分成多個文件。

多個文件箱

如果每個箱只是一個文件,那么這些文件會很大。我們常常很容易將箱分成多個文件,并且 Rust 從兩個方面來支持這樣做。

不是像下面這樣聲明一個模塊:

    mod english {
    // contents of our module go here
    }
    ?

相反我們可以這樣聲明我們的模塊:

    mod english;

如果我們這樣做,Rust 將期望找到一個 english.rs 文件,或者是含有我們的模塊的內容的 english/mod.rs 文件。

注意,在這些文件中,您不需要 re-declare 模塊:這些已經由最初的模塊的聲明了。

使用這兩種技巧,我們可以把箱子拆分成兩個目錄和七個文件:

image

src/lib.rs 是我們箱根,看起來像這樣:

    mod english;
    mod japanese;

這兩個聲明告訴 Rust 根據(jù)我們的偏好去尋找 src/english.rssrc/japanese.rs, 或者 src/english/mod.rssrc/japanese/mod.rs。在這種情況下,由于我們的模塊有子模塊,我們就選擇第二個。src/english/mod.rssrc/japanese/mod.rs 看起來都像這樣:

    mod greetings;
    mod farewells; 

再一次,這些聲明告訴 Rust 去尋找 src/english/greetings.rssrc/japanese/greetings.rs 或者 src/english/farewells/mod.rssrc/japanese/farewells/mod.rs。因為這些子模塊沒有自己的子模塊,我們選擇讓他們 src/english/greetings.rssrc/japanese/farewells.rs

src/english/greetings.rssrc/japanese/farewells.rs 的內容在此時都是空的。讓我們添加一些函數(shù)。

把下面這些放到 src/english/greetings.rs 里面:

    fn hello() -> String {
    "Hello!".to_string()
    }
    把下面這些放到src/english/farewells.rs:
    fn goodbye() -> String {
    "Goodbye.".to_string()
    }
    src/japanese/greetings.rs:
    fn hello() -> String {
    "こんにちは".to_string()
    }

當然,你可以從這個網頁復制和粘貼這些或者自己敲一些其他的東西。你用 “konnichiwa” 還是其他的什么學習模塊系統(tǒng)實際上并不重要。

把下面這些放到 src /日本/ farewells.rs

    fn goodbye() -> String {
    "さようなら".to_string()
    }

(如果你好奇的話,可以告訴你這是 “Sayōnara”。)

現(xiàn)在,我們的箱具有一些功能,讓我們試著從另一個箱使用這些功能。

導入外部箱

我們有一個庫箱。讓我們做一個可執(zhí)行的箱,這個箱導入和并且使用我們的庫。

生成一個 src/main.rs 并且把下面這些代碼輸進去(此時還不會完全編譯):

    extern crate phrases;

    fn main() {
    println!("Hello in English: {}", phrases::english::greetings::hello());
    println!("Goodbye in English: {}", phrases::english::farewells::goodbye());

    println!("Hello in Japanese: {}", phrases::japanese::greetings::hello());
    println!("Goodbye in Japanese: {}", phrases::japanese::farewells::goodbye());
    }

外面的箱聲明告訴,我們需要編譯和鏈接短語箱。我們可以使用短語“模塊。如前所述,您可以使用雙冒號來引用子模塊的內部功能。

另外, Cargo 假設 src/main.rs 是一個二進制箱的根箱,而不是一個箱庫。我們的包現(xiàn)在有兩個箱: src/lib.rs 以及 src/main.rs。對可執(zhí)行文件箱來說,這種模式是很常見的:大多數(shù)功能都是在庫箱里面,并且可執(zhí)行箱將使用這個庫。在這種方式下,其他程序也可以使用庫箱,這也是一個不錯的關注點分離方法。

然而這并不管用。我們得到了類似下面四個的錯誤:

    $ cargo build
       Compiling phrases v0.0.1 (file:///home/you/projects/phrases)
    src/main.rs:4:38: 4:72 error: function `hello` is private
    src/main.rs:4 println!("Hello in English: {}", phrases::english::greetings::hello());

    note: in expansion of format_args!
    <std macros>:2:25: 2:58 note: expansion site
    <std macros>:1:1: 2:62 note: in expansion of print!
    <std macros>:3:1: 3:54 note: expansion site
    <std macros>:1:1: 3:58 note: in expansion of println!
    phrases/src/main.rs:4:5: 4:76 note: expansion site

默認情況下,在 Rus t語言里面一切都是非公開的。讓我們從更深的層次來談一下。

導出一個公共接口

在默認情況下,Rust 可以精確地控制你的接口的哪些方面是公開的,哪些方面是非公開的。要把某些事物公開,你需要使用使用 pub 關鍵字。讓我們首先關注 english 模塊,然后讓我們減小我們的 src/main.rs 到下面這樣:

    extern crate phrases;

    fn main() {
    println!("Hello in English: {}", phrases::english::greetings::hello());
    println!("Goodbye in English: {}", phrases::english::farewells::goodbye());
    }

在我們的 src/english/mod.rs 里面,讓我們添加 pub 到英語模塊聲明里面:

    pub mod english;
    mod japanese;

并且在我們的 src/english/mod.rs 里面,我們寫兩個 pub 語句:

    pub mod greetings;
    pub mod farewells;

在我們的 src/english/greetings.rs 里面,我們添加 pub 到 fn 的聲明里面:

pub fn hello() -> String {
"Hello!".to_string()
}

也在 src/english/farewells.rs 里面這樣做:

    pub fn goodbye() -> String {
    "Goodbye.".to_string()
    }

現(xiàn)在,雖然有警告告訴我們不能使用帶有日語的函數(shù),我們的箱依然進行編譯:

    $ cargo run
       Compiling phrases v0.0.1 (file:///home/you/projects/phrases)
    src/japanese/greetings.rs:1:1: 3:2 warning: function is never used: `hello`, #[warn(dead_code)] on by default
    src/japanese/greetings.rs:1 fn hello() -> String {
    src/japanese/greetings.rs:2 "こんにちは".to_string()
    src/japanese/greetings.rs:3 }
    src/japanese/farewells.rs:1:1: 3:2 warning: function is never used: `goodbye`, #[warn(dead_code)] on by default
    src/japanese/farewells.rs:1 fn goodbye() -> String {
    src/japanese/farewells.rs:2 "さようなら".to_string()
    src/japanese/farewells.rs:3 }
     Running `target/debug/phrases`
    Hello in English: Hello!
    Goodbye in English: Goodbye.

現(xiàn)在,我們的函數(shù)是公開的,我們可以使用它們。太棒了!然而,輸入 phrases::english::greetings::hello() 太長而且重復。Rust 還有另一個關鍵字可以導入名稱到當前的范圍,這樣你可以用更短的名字來引用他們。讓我們談談 use。

用 use 導入模塊

Rust 有一個 use 關鍵字,它允許我們將名稱導入本地范圍。讓我們改變我們的 src/main.rs 成下面這樣:

    extern crate phrases;

    use phrases::english::greetings;
    use phrases::english::farewells;

    fn main() {
    println!("Hello in English: {}", greetings::hello());
    println!("Goodbye in English: {}", farewells::goodbye());
    }

兩個 use 行將每個模塊導入到本地范圍,所以我們可以用更短的名稱來調用函數(shù)。按照慣例,在導入功能時,通常認為最好的做法是導入模塊而不是直接導入函數(shù)。換句話說,你可以這樣做:

    extern crate phrases;

    use phrases::english::greetings::hello;
    use phrases::english::farewells::goodbye;

    fn main() {
    println!("Hello in English: {}", hello());
    println!("Goodbye in English: {}", goodbye());
    }

但它不是慣用的方法。這是更有可能引入命名沖突。在我們的短程序里面,這不是一個大問題,但是當它在大型程序里面就變成一個問題了。如果我們有相互矛盾的名字,Rust 會給出一個編譯錯誤。例如,如果我們用 public 修飾日語函數(shù),并試圖做到這一點:

    extern crate phrases;

    use phrases::english::greetings::hello;
    use phrases::japanese::greetings::hello;

    fn main() {
    println!("Hello in English: {}", hello());
    println!("Hello in Japanese: {}", hello());
    }

Rust 將給我們一個編譯時的錯誤:

    Compiling phrases v0.0.1 (file:///home/you/projects/phrases)
    src/main.rs:4:5: 4:40 error: a value named `hello` has already been imported in this module [E0252]
    src/main.rs:4 use phrases::japanese::greetings::hello;

    error: aborting due to previous error
    Could not compile `phrases`.

如果我們從相同的模塊導入多個名稱,我們不需要輸入兩次。不必像下面這樣:

    use phrases::english::greetings;
    use phrases::english::farewells;

我們可以使用這個快捷鍵:

    use phrases::english::{greetings, farewells};

用 pub use 重新導出

你不要只是使用 use 關鍵字來縮短標識符。您還可以在你的箱里使用它去再次導入一個在另一個模塊里的函數(shù)。這允許您呈現(xiàn)一個外部接口,并且這個接口可以并不直接映射到您的內部代碼組織。

讓我們來看一個例子。修改您的 src/main.rs 像下面這樣:

    extern crate phrases;

    use phrases::english::{greetings,farewells};
    use phrases::japanese;

    fn main() {
    println!("Hello in English: {}", greetings::hello());
    println!("Goodbye in English: {}", farewells::goodbye());

    println!("Hello in Japanese: {}", japanese::hello());
    println!("Goodbye in Japanese: {}", japanese::goodbye());
    }

然后,修改您的 src/lib.rs 使日語 mod 公開:

    pub mod english;
    pub mod japanese;

接下來,公開這兩個函數(shù),首先在 src/japanese/greetings.rs 里面:

    pub fn hello() -> String {
    "こんにちは".to_string()
    }

然后在 src/japanese/farewells.rs 里面:

    pub fn goodbye() -> String {
    "さようなら".to_string()
    }

最后,修改您的 src/japanese/mod.rs 成下面這樣:

    pub use self::greetings::hello;
    pub use self::farewells::goodbye;

    mod greetings;
    mod farewells;

在我們模塊的層次結構的這一部分,pub use 將在這個范圍內聲明函數(shù)。因為我們在日語模塊里面已經這樣做了,我們現(xiàn)在有一個 phrases::japanese::hello() 函數(shù) 和 一個 phrases::japanese::goodbye() 函數(shù),雖然他們的代碼存在于 phrases::japanese::greetings::hello() 和 phrases::japanese::farewells::goodbye()。我們內部組織不能定義我們的外部接口。

在這里,每個我們想納入日語范圍的函數(shù)都有一個 pub use。我們也可以使用通配符語法去吧 greating 里面的所有東西列入到當前范圍: pub use self::greetings::*。

那 sel f呢?默認情況下,從你的箱根開始,use 的聲明都是絕對路徑。相反,self 使這條路是一條相對于當前的層次結構的相對路徑。還有一個 use 的特殊形式:您可以使用 super:: 達到你所在的樹的當前層次的上一層。從許多 shell 的當前目錄和父目錄所顯示的來看,有些人經常認為 self是.而 super是. .。

在 Use 的外部,路徑是相對的: 相對于我們所處的位置,foo::bar() 指向的是一個 foo內部的函數(shù)。如果那是一個 :: 前綴,就像 ::foo:bar()里面的,那么它指的是一個不同的 foo,是一條從你的箱根開始的絕對路徑。

同時,注意,我們在聲明 mod 之前就 pub use 了。Rust 語言要求首先進行 use 的聲明。

這將構建并運行下面的代碼:

    $ cargo run
       Compiling phrases v0.0.1 (file:///home/you/projects/phrases)
     Running `target/debug/phrases`
    Hello in English: Hello!
    Goodbye in English: Goodbye.
    Hello in Japanese: こんにちは
    Goodbye in Japanese: さようなら
以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號