Rust語言使用模塊系統來組織工程和代碼。模塊系統允許我們將相關的函數、類型、常量等組織在一起,形成一個邏輯上的單元。通過模塊系統,我們可以隱藏實現細節,只暴露必要的接口,從而提高代碼的可讀性和可維護性。Rust的模塊系統還支持路徑依賴和重導出等功能,使得代碼的組織更加靈活和方便。
Rust的模塊系統中有三個非常重要的概念,分別是:箱(Crate)、包(Package)和模塊(Module),下面逐一進行介紹。
箱(Crate)箱,英文爲Crate,是Rust中的編譯單元和構建單元,也是Cargo打包和分發的基本單位。Crate可以是庫(library crate),也可以是二進制程序(binary crate)。庫crate包含了可以被其他crate使用的代碼,二進制crate則包含了可以執行的程序。每個crate都有一個crate root,它是編譯器開始構建crate模塊樹的源文件。對于庫crate,crate root通常是src/lib.rs文件;對于二進制crate,crate root通常是src/main.rs文件。
通過crate,我們可以將代碼進一步拆分成更小的、更易于管理和維護的單元。當在Cargo中創建一個新的項目時,實際上就是在創建一個Crate。通過cargo new my_crate命令,Cargo將爲我們初始化一個新的Crate結構,其中包括:源碼目錄、測試文件、Cargo.toml配置文件等。在Rust中,Crate是編譯時的概念,它指代的是編譯後生成的一個單元,可以是一個庫或者一個可執行程序。
包(Package)包,英文爲Package,是Cargo用于組織和構建代碼的基本單位。每個Rust項目都包含至少一個Package,並通過名爲Cargo.toml的配置文件來描述其屬性和依賴關系。Package的元數據存儲在Cargo.toml文件中,這個文件包含了關于Package的基本信息,比如:名稱、版本、作者、描述、許可證等。另外,Cargo.toml還列出了Package的依賴項,這些依賴項是其他Packages或Crates,它們會被Cargo自動下載和構建。
Package通常包含源碼目錄,包括但不限于src目錄下的main.rs或lib.rs。如果項目更複雜,還可以有多個模塊文件和子模塊文件夾。一個Package可以包含一個或多個Crates,但通常情況下,一個簡單的Package會對應一個單一的Crate。當通過cargo build命令構建項目時,最終輸出的二進制文件或庫文件就是這個Crate。
模塊(Module)模塊,英文爲Module,是用于在crate內部進行分層和封裝的機制。模塊內部又可以包含模塊,從而形成一個樹形結構,也稱爲模塊樹。每個crate會自動産生一個與當前crate同名的模塊,作爲這個樹形結構的根節點。模塊是元素(比如:函數、結構體、trait等)的集合,是一種抽象的概念,而文件則是承載這個概念的實體。
在Rust中,創建新模塊主要有以下三種方式。
1、在一個文件中創建內嵌模塊。這可以通過直接使用mod關鍵字來實現,模塊的內容會被包含在大括號內部。
2、獨立的一個文件就是一個模塊,文件名即是模塊名。
3、一個文件夾也可以代表一個模塊。在這種情況下,有兩種方法可以實現:
(1)文件夾內部需要有一個名爲mod.rs的文件,這個文件就是這個模塊的入口。在rustc 1.30版本之前,這是唯一的方法。
(2)在文件夾同級目錄裏創建一個與模塊(文件夾)同名的rs文件。在rustc 1.30版本之後,更建議使用這樣的命名方式,以避免項目中存在大量同名的mod.rs文件。
模塊樹模塊樹是一個邏輯上的分層結構,它反映了源代碼文件的組織方式。每個Rust項目都可以看作一個模塊樹的根,其中包含零個或多個子模塊。每個模塊可以進一步包含其他的子模塊,從而形成嵌套的層次結構。
在下面的示例模塊樹中,lib.rs是crate的根模塊,shapes和math是它的子模塊。circle和rectangle是shapes的子模塊,algebra和geometry是math的子模塊。shapes之所以是模塊,是因爲shapes文件夾下有一個mod.rs文件。math之所以是模塊,是因爲math同級目錄下有一個同名的math.rs文件。在後面內容的介紹當中,我們也會用到這裏的示例模塊樹。
project/├── src/│ ├── lib.rs // crate根模塊│ ├── shapes/│ │ ├── mod.rs // shapes模塊│ │ ├── circle.rs│ │ └── rectangle.rs│ ├── math/│ │ ├── algebra.rs│ │ └── geometry.rs│ └── math.rs // math模塊
模塊路徑在Rust中,模塊路徑是用于唯一標識模塊中定義的元素(比如:函數、結構體等)的字符串。模塊路徑由一系列由雙冒號(::)分隔的標識符組成,從crate根開始,一直到指定的項,可以是絕對路徑或相對路徑。
絕對路徑:以crate::開始,表示從crate根開始的完整路徑。在下面的示例代碼中,crate::shapes::circle::Area表示從crate根開始的shapes子模塊、circle子目錄的Area函數。
use crate::shapes::circle::Area;
相對路徑:直接使用模塊名稱表示同級模塊,或者相對于當前模塊的子模塊。有兩個特殊的標識需要記住,self::表示當前模塊,super::表示當前模塊的父模塊。
// 在shapes/mod.rs中引用circle.rs中的內容use self::circle::Area;// 在circle.rs中引用shapes/mod.rs中定義的公共常量DEFAULT_RADIUSuse super::DEFAULT_RADIUS;// 在同一目錄下引用rectangle模塊use rectangle::Rectangle;
訪問權限在Rust中,訪問權限是通過pub關鍵字來控制的。默認情況下,如果不加修飾符,模塊中的成員訪問權將是私有的。這意味著,它們只能在定義它們的模塊內部被訪問。如果想讓其他模塊能夠訪問某個成員,就需要在該模塊和該成員前加上pub關鍵字來聲明其爲公開的。
訪問權限主要有兩種:一種是模塊級的訪問權限,另一種是成員級別的訪問權限。
1、模塊級的訪問權限。公開模塊可以在任何地方被訪問,只要我們知道正確的路徑。私有模塊只能在與其平級的位置,或下級的位置被訪問。也就是說,如果一個模塊是私有的,那麽只有在其同級模塊或子模塊中才能引用它。
2、成員級別的訪問權限。使用pub關鍵字標記的成員是公開的,可以在其他模塊中通過路徑來訪問。沒有使用pub關鍵字標記的成員是私有的,只能在定義它們的模塊內部訪問。
// 公開模塊pub mod public_module { // 公開函數,可以在其他模塊中訪問 pub fn public_function() { } // 私有函數,只能在本模塊內部訪問 fn private_function() { }}// 私有模塊mod private_module { // 這個模塊是私有的,不能在其他模塊中直接訪問 fn private_function() { }}fn main() { public_module::public_function();}
除此之外,Rust還提供了更細粒度的訪問控制,允許我們指定一個成員僅在crate內部可見,或者僅在特定的模塊及其子模塊中可見。pub(crate)表示該成員在當前crate的任何地方都可見,但在外部crate中不可見。pub(in module)表示該成員在指定的模塊及其子模塊中可見,在其他模塊不可見。
// 函數僅在當前crate內可見pub(crate) fn crate_function() {}// 公開模塊pub mod my_module { // 函數僅在當前模塊及其子模塊中可見 pub(in crate::my_module) fn module_function() { } pub fn public_function() { // 可以調用crate_function crate::crate_function(); // 可以調用module_function module_function(); }} // 另一個模塊mod another_module { pub fn another_function() { crate::crate_function(); // 下面的代碼會提示編譯錯誤:function `module_function` is private super::my_module::module_function(); }}fn main() { crate_function();}
如果模塊中定義了結構體,那麽結構體本身以及它的字段默認都是私有的。如果希望結構體的某個字段能夠被外部訪問,則需要在結構體和該字段前均加上pub關鍵字。枚舉類型則不同,只需要在枚舉類型前加上pub關鍵字,而不需要在枚舉成員前加上pub關鍵字。
使用useuse關鍵字用于導入模塊或庫中的元素(比如:函數、結構體等),以便在當前作用域中使用它們而無需使用完全限定的名稱。use語句通常放在文件的頂部,緊接在模塊聲明之後。
use關鍵字的使用方式主要以下幾種。
1、導入整個模塊。可以使用use來導入整個模塊,這樣我們就可以直接使用該模塊中公開的成員。
// 導入std模塊中的vec模塊use std::vec;fn main() { // 直接使用vec!宏 let value = vec![1, 2, 3]; println!("{:?}", value);}
2、導入特定項。可以使用use來導入模塊中的特定項,而不是整個模塊。
// 只導入HashMapuse std::collections::HashMap;
3、重命名導入的項。如果導入的元素在當前作用域中已經存在同名項,或者想要使用不同的名稱來引用它,我們可以使用as關鍵字來重命名。
// 重命名HashMap爲:MyMapuse std::collections::HashMap as MyMap;
4、使用通配符導入。使用*可以導入模塊中所有公開的成員,但需要注意的是,過度使用通配符導入可能會導致名稱沖突和不可預見的行爲,因此通常建議明確導入你需要的元素。
// 導入std::collections模塊中的所有公開成員use std::collections::*;
5、多個use語句可以組合在一起,以提高便捷性和可讀性。
use std::{ fs::File, io::{self, Write},};
總結Rust的模塊系統是其代碼組織管理的核心部分,它提供了一種方式來封裝和組織代碼,控制作用域和路徑的私有性,以及導出公共接口。模塊系統使得開發者能夠構建大型、複雜的應用程序,同時保持代碼的清晰性和可維護性。