包、crate 與 模塊
編寫程序時一個核心的問題是作用域:在代碼的某處編譯器知道哪些變量名缸濒?允許調(diào)用哪些函數(shù)足丢?這些變量引用的又是什么元镀?
Rust
有一系列與作用域相關(guān)的功能。這有時被稱為模塊系統(tǒng)霎桅,不過又不僅僅是模塊:
包是Cargo的一個功能栖疑,它允許你構(gòu)建、測試和分享crate滔驶。
Crates是一個模塊的樹形結(jié)構(gòu)遇革,它形成了庫或二進制項目。
模塊和
use
關(guān)鍵字允許你控制作用域和路徑的私有性揭糕。路徑是一個命名例如結(jié)構(gòu)體萝快、函數(shù)或模塊等項的方式
包和 crate 用來創(chuàng)建庫和二進制項目
讓我們來聊聊模塊和 crate。下面是一個總結(jié):
- crate 是一個二進制或庫項目著角。
- crate 根是一個用來藐視如何構(gòu)建 crate 的文件揪漩。
- 帶有 Cargo.toml 文件的包用以描述如何構(gòu)建一個或多個 crate。一個包中至多有一個庫項目吏口。
所以當運行cargo new
時是在創(chuàng)建一個包:
cargo new my-project
因為 Cargo 創(chuàng)建了 Cargo.toml奄容,這意味者現(xiàn)在我們有了一個包。如果查看 Cargo.toml 的內(nèi)容产徊,會發(fā)現(xiàn)并沒有提到 src/main.rs昂勒。然而,Cargo 的約定是如果在代表表的 Cargo.toml 的同級目錄下包含 src 目錄且其中包含 main.rs 文件的話舟铜,Cargo 就知道這個包帶有一個與包同名的二進制 crate戈盈,且 src/main.rs 就是 crate 根。另一個約定如果包目錄中包含 src/lib.rs谆刨,則包帶有與其同名的庫 crate塘娶,且 src/lib.rs 是 crate 根。crate 根文件將由 Cargo 傳遞給 rustc
來實際構(gòu)建庫或者二進制項目痊夭。
一個包可以帶有零個或一個庫 crate 和任意多個二進制 crate刁岸。一個包中至少一個(庫或二進制) crate。
模塊系統(tǒng)來控制作用域和私有性
Rust 的此部分功能通常被引用為模塊系統(tǒng)生兆,不過其包括了一些除模塊之外的功能难捌。本部分我們會討論:
- 模塊,一個組織代碼和控制路徑私有性的方式
- 路徑鸦难,一個命名項的方式
-
use
關(guān)鍵字用來將路徑引入作用域 -
pub
關(guān)鍵字使項變?yōu)楣灿?/li> -
as
關(guān)鍵字用于將項引入作用域時進行重命名 - 使用外部包
- 嵌套路徑來消除大量的
use
語句 - 使用 glob 運算符將模塊的所有內(nèi)容引入作用域
- 如何將不同模塊分割到單獨的文件中
模塊允許我們將代碼組織起來根吁。
mod sound {
mod instrument {
mod woodwind {
fn clarinet() {
}
}
}
mod voice {
}
}
fn main() {
}
路徑來引用模塊樹中的項
如果想要調(diào)用函數(shù),需要知道其路徑合蔽。
路徑有兩種形式:
-
絕對路徑從crate 根開始击敌,以 crate 名或者字面值
crate
開頭 -
相對路徑從當前模塊開始,以
self
拴事、supper
或者當前模塊的標識符開頭沃斤。
絕對路徑和相對路徑都后跟一個或多個由雙冒號(::
)分割的標識符圣蝎。
在main
中調(diào)用clarinet
函數(shù),現(xiàn)在還不能調(diào)用
mod sound {
mod instrument {
fn clarinet() {
}
}
}
fn main() {
// 絕對路徑
crate::sound::instrument::clarinet();
// 相對路徑
sound::instrument::clarinet();
}
instrument
模塊是私有的衡瓶。instrument
模塊和clarinet
函數(shù)的路徑都是正確的徘公,不過Rust
不讓我們使用,因為他們是私有的哮针。
模塊作為私有性的邊界
Rust
采用模塊還有另外一個原因:模塊是Rust
中的私有邊界关面。私有規(guī)則如下:
- 所有項(函數(shù)、方法十厢、結(jié)構(gòu)體等太、枚舉、模塊和常量)默認是私有的蛮放。
- 可以使用
pub
關(guān)鍵字使項變?yōu)楣灿小?/li> - 不允許使用定義于當前模塊的子模塊中私有代碼缩抡。
- 允許使用任何定義于父模塊或當前模塊中的代碼
使用pub
關(guān)鍵字使項變?yōu)楣?/h3>
mod sound {
pub mod instrument {
pub fn clarinet() {
}
}
}
fn main() {
// 絕對路徑
crate::sound::instrument::clarinet();
// 相對路徑
sound::instrument::clarinet();
}
使用super
開始相對路徑
fn main() {
crate::sound::instrument::clarinet();
sound::instrument::inner::clarinet();
}
mod sound {
pub mod instrument {
pub fn clarinet() {
super::voice::hello();
}
pub mod inner {
pub fn clarinet() {
super::super::voice::hello();
}
}
}
pub mod voice {
pub fn hello() {
println!("hello");
}
}
}
對結(jié)構(gòu)體和枚舉使用pub
mod plant {
pub struct Vegetable {
pub name: String,
id: i32,
}
impl Vegetable {
pub fn new(name: &str) -> Vegetable {
Vegetable {
name: String::from(name),
id: 1,
}
}
}
}
fn main() {
let mut v = plant::Vegetable::new("squash");
v.name = String::from("butternut squash");
println!("{}", v.name);
}
mod sound {
pub mod instrument {
pub fn clarinet() {
}
}
}
fn main() {
// 絕對路徑
crate::sound::instrument::clarinet();
// 相對路徑
sound::instrument::clarinet();
}
super
開始相對路徑fn main() {
crate::sound::instrument::clarinet();
sound::instrument::inner::clarinet();
}
mod sound {
pub mod instrument {
pub fn clarinet() {
super::voice::hello();
}
pub mod inner {
pub fn clarinet() {
super::super::voice::hello();
}
}
}
pub mod voice {
pub fn hello() {
println!("hello");
}
}
}
pub
mod plant {
pub struct Vegetable {
pub name: String,
id: i32,
}
impl Vegetable {
pub fn new(name: &str) -> Vegetable {
Vegetable {
name: String::from(name),
id: 1,
}
}
}
}
fn main() {
let mut v = plant::Vegetable::new("squash");
v.name = String::from("butternut squash");
println!("{}", v.name);
}
plant::Vegetable
結(jié)構(gòu)體的name
字段是公有的,在main
中可以使用點號讀寫name
字段包颁。不允許在main
中使用id
字段因為是私有的瞻想。
使用use
關(guān)鍵字將名稱引入作用域
mod sound {
pub mod instrument {
pub fn clarinet() {
}
}
}
use crate::sound::instrument;
fn main() {
instrument::clarinet();
}
使用相對路徑將項引入作用域
use self::sound::instrument;
user
函數(shù)路徑使用習慣 VS 其他項
mod sound {
pub mod instrument {
pub fn clarinet() {
}
}
}
use crate::sound::instrument::clarinet;
fn main() {
clarinet();
}
user
將clarinet
函數(shù)引入作用域,這是不推薦的徘六。
對于函數(shù)來說内边,通過use
指定的父模塊接著指定模塊來調(diào)用方法被認為是習慣的方法。通過use
指定函數(shù)的路徑待锈,清楚的表明了函數(shù)不是本地定義的,同時最小化了指定全路徑的重復嘴高。
通過 as
關(guān)鍵字重命名引入作用域的類型
將兩個同名類型引入同一作用域這個問題還有另一個解決辦法:可以通過在use
后加上as
和一個新名稱來為此類型指定一個新的本地名稱竿音。
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
Ok(())
}
fn function2() -> IoResult<()> {
Ok(())
}
通過pub use
重導出名稱
當使用 use
關(guān)鍵字將名稱導入作用域時,在新作用域中可用的名稱是私有的拴驮。如果希望調(diào)用呢編寫的代碼能夠像你一樣在其自己的作用域內(nèi)引用這些類型春瞬,可以結(jié)合pub
和use
。這個技術(shù)被稱為重導出套啤,因為這樣做將項引入作用域同時使其可供其他代碼引入自己的作用域宽气。
mod sound {
pub mod instrument {
pub fn clarinet() {
}
}
}
mod performance_group {
pub use crate::sound::instrument;
pub fn clarinet_trio() {
instrument::clarinet();
}
}
fn main() {
performance_group::clarinet_trio();
performance_group::instrument::clarinet();
}
使用外部包
在 Cargo.toml 中加入 rand
依賴
[dependencies]
rand = "0.5.5"
將rand
定義引入項目包的作用域,加入一行use
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1, 101);
}
嵌套路徑來消除大量的use
行
當需要引入很多定義于相同或相同模塊的項時潜沦,為每一項單獨列出一行會占用源碼很大的空間萄涯。
use std::cmp::Ordering;
use std::io;
// ...
可以使用嵌套的路徑將同樣的項在一行中引入而不是兩行。
use std::{cmp::Ordering, io};
通過 glob 運算符將所有的公有定義引入作用域
如果希望將一個路徑下 所有 公有項引入作用域唆鸡,可以指定路徑后跟*
,glob 運算符:
use std::collections::*;
將模塊分割進不同文件
當模塊變得更大時涝影,你可能想要將它們的定義移動到一個單獨的文件中使代碼更容易閱讀。
文件名: src/main.rs
mod sound;
fn main() {
// 絕對路徑
create::sound::instrument::clarinet();
// 相對路徑
sound::instrument::clarinet();
}
在crate根文件聲明 sound
模塊(這里是 src/main.rs)争占,將模塊內(nèi)容移動到 src/sound.rs 文件燃逻, src/sound.rs 中會包含sound
模塊的內(nèi)容
文件名: src/sound.rs
pub mod instrument {
pub fn clarinet() {
}
}
在mod sound
后使用分號而不是代碼塊告訴Rust在另一個與模塊同名文件中加載模塊的內(nèi)容序目。
繼續(xù)重構(gòu)我們的例子,將instrument
模塊也提取到自己的文件中伯襟,修改 src/sound.rs 只包含instrument
模塊的聲明:
文件名: src/sound.rs
pub mod instrument;
接著創(chuàng)建 src/sound 目錄和 src/sound/instrument.rs 文件來包含instrument
模塊的定義:
文件名: src/sound/instrument.rs
pub fn clarinet() {
}
模塊樹依然保持相同贮懈,main
中的函數(shù)調(diào)用也無需修改繼續(xù)保持有效,即使其定義存在于不同的文件中伟端。這樣隨著代碼增長可以將模塊移動到新文件中藕各。
總結(jié)
Rust 提供了將包組織進 crate, 將 crate 組織進模塊和通過指定絕對或相對路徑從一個模塊引用另一個模塊中定義的項的方式片效『炻祝可以通過use
語句將路徑引入作用域,這樣在多次使用時可以使用更短的路徑淀衣。模塊定義的代碼默認是私有的昙读,不過可以選擇增加pub
關(guān)鍵字使其定義變?yōu)楣小?/p>