[TOC]
Rust中使用模塊組織代碼
Rust中的模塊化編程
自上個世紀 90 年代以來裕坊,軟件工程的復(fù)雜性越來越高谴餐,程序漸漸從一個人的獨狼開發(fā)轉(zhuǎn)為多人團隊協(xié)作開發(fā)累舷。在今天蝇摸,通過 Github 或中心化的代碼分發(fā)網(wǎng)站橘洞,我們可以輕松的在一個軟件工程中同時引入世界各地的開發(fā)者開發(fā)的代碼捌袜,我們與同事在同一個工程目錄下并行開發(fā)不同的程序功能,或者在不拷貝代碼的前提下將一個工程中的代碼在另一個工程中復(fù)用炸枣。這一切都是因為模塊化編程虏等。
模塊化編程弄唧,是強調(diào)將計算機程序的功能分離成獨立的和可相互改變的“模塊”的軟件設(shè)計技術(shù),它使得每個模塊都包含著執(zhí)行預(yù)期功能的一個唯一方面所必需的所有東西霍衫,復(fù)雜的系統(tǒng)被分割為小塊的獨立代碼塊候引。
Rust 項目的代碼組織包含以下三個基本概念:
- Package(包)
- Crate(箱)
- Module(模塊)
Package
Package 用于管理一個或多個 Crate。創(chuàng)建一個 Package 的方式是使用 cargo new
敦跌,我們來看看一個 Package 包含哪些文件澄干。
$ cargo new my-project
Created binary (application) `my-project` package
$ ls my-project
Cargo.toml
src
$ ls my-project/src
main.rs
當(dāng)我們輸入命令時,Cargo 創(chuàng)建了一個目錄以及一個 Cargo.toml
文件柠傍,這就是一個 Package麸俘。默認情況下,src/main.rs
是與 Package 同名的二進制 Crate 的入口文件惧笛,因此我們可以說我們現(xiàn)在有一個 my-project Package 以及一個二進制 my-project Crate疾掰。同樣,如果在創(chuàng)建 Package 的時候帶上 --lib
徐紧,那么 src/lib.rs
將是它的 Crate 入口文件静檬,且它是一個庫 Crate。
如果我們的 src 目錄中同時包含 main.rs
和 lib.rs
并级,那么我們將在這個 Package 中同時得到一個二進制 Crate 和一個庫 Crate拂檩,這在開發(fā)一些基礎(chǔ)庫時非常有用,例如你使用 Rust 中實現(xiàn)了一個 MD5 函數(shù)嘲碧,你既希望這個 MD5 函數(shù)能作為庫被別人引用稻励,又希望你能獲得一個可以進行 MD5 計算的命令行工具:那就同時添加 main.rs
和 lib.rs
吧!
Crate
Crate 是 Rust 的最小編譯單元愈涩,即 Rust 編譯器是以 Crate 為最小單元進行編譯的望抽。Crate 在一個范圍內(nèi)將相關(guān)的功能組合在一起,并最終通過編譯生成一個二進制或庫文件履婉。例如煤篙,我們在上一章中實現(xiàn)的猜數(shù)字游戲就使用了 rand 依賴,這個 rand 就是一個 Crate毁腿,并且是一個庫的 Crate辑奈。
Module
Module 允許我們將一個 Crate 中的代碼組織成獨立的代碼塊,以便于增強可讀性和代碼復(fù)用已烤。同時鸠窗,Module 還控制代碼的可見性,即將代碼分為公開代碼和私有代碼胯究。公開代碼可以在項目外被使用稍计,私有代碼則只有項目內(nèi)部的代碼才可以訪問。定義一個模塊最基本的方式是使用 mod 關(guān)鍵字:
mod mod1 {
pub mod mod2 {
pub const MESSAGE: &str = "Hello World!"
// ...
}
// ...
}
fn main() {
println!(mod1::mod2::MESSAGE);
}
使用pub改變可見性
Rust 中模塊內(nèi)部的代碼裕循,結(jié)構(gòu)體臣嚣,函數(shù)等類型默認是私有的净刮,但是可以通過 pub 關(guān)鍵字來改變它們的可見性。通過選擇性的對外可見來隱藏模塊內(nèi)部的實現(xiàn)細節(jié)茧球。
比較常用的三種 pub 寫法:
- pub:成員對模塊可見
- pub(self):成員對模塊內(nèi)的子模塊可見
- pub(crate):成員對整個 crate 可見
如果不使用 pub 聲明庭瑰,成員默認的可見性是私有的:
mod mod1 {
pub const MESSAGE: &str = "Hello World!";
const NUMBER: u32 = 42;
pub(self) fn mod1_pub_self_fn() {
println!("{}", NUMBER);
}
pub(crate) enum CrateEnum {
Item = 4,
}
pub mod mod2 {
pub const MESSAGE: &str = "Hello World!";
pub fn mod2_fn() {
super::mod1_pub_self_fn();
}
}
}
fn main() {
println!("{}", mod1::MESSAGE);
println!("{}", mod1::mod2::MESSAGE);
mod1::mod2::mod2_fn();
println!("{}", mod1::CrateEnum::Item as u32);
}
結(jié)構(gòu)體的可見性
結(jié)構(gòu)體中的字段和方法默認是私有的星持,通過加上 pub 修飾語可使得結(jié)構(gòu)體中的字段和方法可以在定義結(jié)構(gòu)體的模塊之外被訪問抢埋。要注意,與結(jié)構(gòu)體同一個模塊的代碼訪問結(jié)構(gòu)體中的字段和方法并不要求該字段是可見的:
mod mod1 {
pub struct Person {
pub name: String,
nickname: String,
}
impl Person {
pub fn new(name: &str, nickname: &str) -> Self {
Person {
name: String::from(name),
nickname: String::from(nickname),
}
}
pub fn say_nick_name(&self) {
println!("{}", self.nickname);
}
}
}
fn main() {
let p = mod1::Person::new("jack", "baby");
println!("{}", p.name);
// println!("{}", p.nickname); // 不能訪問 nickname
p.say_nick_name();
}
使用use綁定模塊成員
正常情況下當(dāng)我們試圖從模塊內(nèi)中訪問其成員時督暂,需要輸入完整的路徑揪垄,例如使用 std::fs::read
從磁盤上讀取文件:
fn main() {
let data = std::fs::read("src/main.rs").unwrap();
println!("{}", String::from_utf8(data).unwrap());
}
可以使用 use 關(guān)鍵詞將完整路徑綁定到一個新的名稱,這可以減少重復(fù)代碼:
use std::fs;
fn main() {
let data = fs::read("src/main.rs").unwrap();
println!("{}", String::from_utf8(data).unwrap());
}
可以使用 as 關(guān)鍵字將導(dǎo)入綁定到一個其他名稱逻翁,它通常用在有多個不同模塊都定義了相同名字的成員時使用:
use std::fs as stdfs;
fn main() {
let data = stdfs::read("src/main.rs").unwrap();
println!("{}", String::from_utf8(data).unwrap());
}
使用super與self簡化模塊路徑
除了使用完整路徑訪問模塊內(nèi)的成員饥努,還可以使用 super 與 self 關(guān)鍵字相對路徑對模塊進行訪問:
- super:上層模塊
- self:當(dāng)前模塊
當(dāng)上層模塊,當(dāng)前模塊或子模塊中擁有相同名字的成員時八回,使用 super 與 self 可以消除訪問時的歧義:
fn function() {
println!("function");
}
pub mod mod1 {
pub fn function() {
super::function();
}
pub mod mod2 {
fn function() {
println!("mod1::mod2::function");
}
pub fn call() {
self::function();
}
}
}
fn main() {
mod1::function();
mod1::mod2::call();
}
項目目錄層次結(jié)構(gòu)
將模塊映射到文件
使用 mod <路徑>
語法酷愧,將一個 rust 源碼文件作為模塊內(nèi)引入:
src
├── main.rs
└── mod1.rs
mod1.rs
:
pub const MESSAGE: &str = "Hello World!";
main.rs
:
mod mod1;
fn main() {
println!("{}", mod1::MESSAGE);
}
將模塊映射到文件夾
當(dāng)一個文件夾中包含 mod.rs
文件時,該文件夾可以被作為一個模塊:
src
├── main.rs
└── mod1
└── mod.rs
mod1/mod.rs
:
pub const MESSAGE: &str = "Hello World!";
main.rs
:
mod mod1;
fn main() {
println!("{}", mod1::MESSAGE);
}