位元詩人 [Rust] 程式設計教學:模組 (Module) 和套件 (Package)

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

我們學會函式後,程式碼可以分離,然而,隨著專案規模上升,函式名稱有可能相互衝突。雖然,我們也可以修改函式名稱,但是,只靠函數名稱來區分函式,往往會造成函數名稱變得冗長。像 C 語言中,沒有額外的機制處理函式名稱的衝突,就會看到很多長名稱的函式,像是 gtk_application_get_windows (出自 GTK+ 函式庫)。Rust 提供模組 (module) 的機制,處理函式命名衝突的問題。

註:在許多程式語言中,以命名空間 (namespace) 提供類似的機制。

在我們先前的內容中,函式和主程式都寫在同一個檔案。在實務上,我們會將函式或物件獨立出來,製成套件 (package),之後可以重覆利用。例如,同一套函式庫,可供終端機或圖形介面等不同使用者介面來使用。在 Rust 中,套件和模組相互關連。Rust 的套件又稱為 crate。

使用模組

雖然我們在先前的內容沒有強調模組,實際上,我們已經在使用模組了。我們回頭看先前的一個例子:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. // Call f64 module
  2. use std::f64;
  3.  
  4. fn main() {
  5.     // Call sqrt function
  6.     let n = f64::sqrt(4.0);
  7.  
  8.     println!("{}", n);
  9. }

在這個例子中,我們呼叫 std 函式庫之中的 f64 模組,之後,就可以呼叫該模組內的 sqrt 函式。

撰寫模組

在 Rust 中,使用 mod 這個關鍵字來建立模組。如下例:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. mod english {
  2.     pub fn hello(name: &str) {
  3.         println!("Hello, {}", name);
  4.     }
  5. }
  6.  
  7. mod chinese {
  8.     pub fn hello(name: &str) {
  9.         println!("你好,{}", name);
  10.     }
  11. }
  12.  
  13. fn main() {
  14.     // Call modules
  15.     use english;
  16.     use chinese;
  17.  
  18.     // Call two functions with the same name
  19.     english::hello("Michael");
  20.     chinese::hello("麥可");
  21. }

在本例中,我們在兩個模組中定義了同名而不同功能的函式。由於這兩個函式被區隔不同的模組中,不會有命名衝突的問題。模組除了區隔函式名稱外,也提供私有區塊,在模組中的函式或物件,需以 pub 關鍵字宣告,否則無法在模組外使用。

如同我們先前看到的範例,模組也可以內嵌。如下例:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. mod english {
  2.     pub mod greeting {
  3.         pub fn hello(name: &str) {
  4.             println!("Hello, {}", name);
  5.         }
  6.     }
  7.  
  8.     pub mod farewell {
  9.         pub fn goodbye(name: &str) {
  10.             println!("Goodbye, {}", name);
  11.         }
  12.     }
  13. }
  14.  
  15. fn main() {
  16.     use english;
  17.  
  18.     english::greeting::hello("Michael");
  19.     english::farewell::goodbye("Michael");
  20. }

由本例可知,透過模組的機制,可以協助我們整理函式。

建立套件

在我們先前的範例中,我們建立的是應用程式專案,如下:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. $ cargo new --bin myapp

但若想將函式或物件獨立出來,供其他 Rust 程式使用,則要用函式庫專案,如下:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. $ cargo new --lib mylib

我們現在實際建立一個函式庫套件。以上述指令建立 mylib 函式庫套件。加入以下函式:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. // mylib/src/lib.rc
  2.  
  3. pub fn hello(name: &str) -> String {
  4.     format!("Hello, {}", name)
  5. }

之後,退回到上一層目錄,建立 myapp 主程式套件。加入以下內容:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. // myapp/src/main.rs
  2.  
  3. // Call mylib
  4. extern crate mylib;
  5.  
  6. fn main() {
  7.     assert_eq!(mylib::hello("Michael"), "Hello, Michael");
  8. }

透過 extern crate 可以呼叫外部專案。另外,要修改 Cargo.toml 紀錄檔,加入以下內容:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. [dependencies]
  2. mylib = { path = "../mylib" }

之後,執行該專案,若可正確執行,代表我們成功地建立套件。

如果函式庫存放在遠端站台上,需修改存取位置。在下例中,我們存取以 Git 存放的函式庫:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. [dependencies]
  2. rand = { git = "https://github.com/rust-lang-nursery/rand.git" }

Cargo.toml 是 Rust 套件 (i.e. crate) 使用的設定檔。建議花一些時間熟悉其官方文件

在套件中使用模組

在我們先前的例子中,透過 mylib 函式庫對函式命名做最基本的區隔。不過,我們也可以在函式庫中使用模組來進一步區隔函式。我們先以實例看加入模組後的效果:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. // Call external library
  2. extern crate phrase;
  3.  
  4. fn main() {
  5.     assert_eq!("Hello, Michael", phrase::english::greeting::hello("Michael"));
  6.     assert_eq!("你好,麥可", phrase::chinese::greeting::hello("麥可"));
  7. }

同樣地,需於 Cargo.toml 加入套件位置:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. [dependencies]
  2. phrase = { path = "../phrase" }

我們現在要實際建立這個函式庫。退回上一層目錄,建立 phrase 函式庫專案:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. $ cargo new --lib phrase

整個 phrase 專案結構如下:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. $ tree
  2. .
  3. ├── Cargo.lock
  4. ├── Cargo.toml
  5. └── src
  6.     ├── chinese
  7.     │   ├── greeting.rs
  8.     │   └── mod.rs
  9.     ├── english
  10.     │   ├── greeting.rs
  11.     │   └── mod.rs
  12.     └── lib.rs

src/lib.rs 中宣告模組,記得要宣告公開權限:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. pub mod english;
  2. pub mod chinese;

src/english/mod.rs 中宣告子模組:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. pub mod greeting;

src/english/greeting.rs 中實作函式:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. pub fn hello(name: &str) -> String {
  2.     format!("Hello, {}", name)
  3. }

同樣地,在 src/chinese/mod.rs 中宣告子模組:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. pub mod greeting;

同樣地,在 src/chinese/greeting.rs 中實作函式:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. pub fn hello(name: &str) -> String {
  2. format!("你好,{}", name)
  3. }

由於 Rust 的模組及套件和檔案名稱是連動的,若使用錯誤的檔案名稱將無法編譯,需注意。

進階的模組使用方式

在先前的例子中,由於函式庫結構較複雜,使得函式呼叫的動作變得繁瑣,Rust 提供別名來簡化這個動作。如下例:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. extern crate phrase;
  2.  
  3. use phrase::english::greeting as en_greeting;
  4. use phrase::chinese::greeting as zh_greeting;
  5.  
  6. fn main() {
  7.     assert_eq!("Hello, Michael", en_greeting::hello("Michael"));
  8.     assert_eq!("你好,麥可", zh_greeting::hello("麥可"));
  9. }

Rust 官方文件中提供了另一個更複雜的模組呼叫範例:

稍微閱讀一下程式碼,大概就知道如何呼叫模組。要注意的是,globbing 的動作,會直接暴露函式名稱到主程式中,喪失使用模組區隔函式名稱的用意,應盡量避免。

關於作者

位元詩人 (ByteBard) 是資訊領域碩士,喜歡用開源技術來解決各式各樣的問題。這類技術跨平台、重用性高、技術生命長。

除了開源技術以外,位元詩人喜歡日本料理和黑咖啡,會一些日文,有時會自助旅行。