【譯】關於Rust模塊的清晰解釋

  • 2020 年 11 月 13 日
  • 筆記

原文鏈接: //www.sheshbabu.com/posts/rust-module-system/

原文標題: Clear explanation of Rust』s module system

公眾號: Rust碎碎念

翻譯: Praying

Rust的模塊(module)系統相當令人困惑,這也給很多初學者帶來了挫敗感。

在本文中,我將會通過實際的例子來解釋模塊系統以便於讓你清晰地理解它是怎樣工作的並且能夠快速在自己的項目中應用。

由於Rust的模塊系統比較獨特,我希望讀者以開放性思維來進行閱讀,並且盡量不要將其與其他語言中的模塊的工作方式進行比較。

讓我們使用下面的文件結構來模擬一個真實世界中的項目:

my_project
├── Cargo.toml
└─┬ src
  ├── main.rs
  ├── config.rs
  ├─┬ routes
  │ ├── health_route.rs
  │ └── user_route.rs
  └─┬ models
    └── user_model.rs

下面是一些使用我們的模塊的不同方式:

下面的3個例子應該足以解釋Rust的模塊系統是如何工作的。

示例1

讓我們從第一個例子開始 —— 在main.rs中導入config.rs

// main.rs
fn main() {
  println!("main");
}
// config.rs
fn print_config() {
  println!("config");
}

很多人常犯的第一個錯誤是因為我們有像config.rshealth_route.rs這樣的文件,所以我們就認為這些文件就是模塊(module)且可以在其他的文件中將其導入。

下面是從我們的視角(文件系統樹(file system tree))看到的內容和從編譯器的角度(模塊樹(module tree))看到的內容:


令人驚奇,編譯器只看到了crate模塊,也就是我們的main.rs文件。這是因為我們需要顯式地在Rust中構建模塊樹——在文件系統樹和模塊樹之間不存在隱式的轉換。

我們需要顯式地在Rust中構建模塊樹——在文件系統樹和模塊樹之間不存在隱式的轉換。

想要把一個文件添加到模塊樹中,我們需要使用mod關鍵字來將這個文件聲明為一個子模塊(submodule)。另一件使人們感到困惑的事情是你會認為在相同的文件里把一個文件聲明為模塊(譯者注:比如使用mod關鍵字把config.rs聲明為子模塊,你可能認為需要在config.rs里來寫聲明)。但是我們需要在一個不同文件里進行聲明!因為我們在這個模塊樹里只有main.rs這個文件,所以要在main.rs里將config.rs聲明為一個子模塊。

mod 關鍵字聲明一個子模塊

mod關鍵字語法如下:

mod my_module;

這裡,編譯器在相同的目錄下查找my_module.rs或者my_module/mod.rs

my_project
├── Cargo.toml
└─┬ src
  ├── main.rs
  └── my_module.rs

或者

my_project
├── Cargo.toml
└─┬ src
  ├── main.rs
  └─┬ my_module
    └── mod.rs

因為main.rsconfig.rs在相同的目錄下,讓我們按照下面的代碼聲明config模塊

// main.rs
mod config;

fn main() {
+ config::print_config();
  println!("main");
}
// config.rs
fn print_config() {
  println!("config");
}

這裡,我們通過::語法使用print_config函數。
下面是模塊樹的樣子:


我們已經成功地聲明了config模塊!但是這還不能調用config.rs里的print_config函數。幾乎Rust裏面的一切默認都是私有(private)的,我們需要使用pub關鍵字來讓這個函數成為公開(public)的:

pub關鍵字使事物公開

// main.rs
mod config;

fn main() {
  config::print_config();
  println!("main");
}
// config.rs
fn print_config() {
pub fn print_config() {
  println!("config");
}

現在,這樣可以正常工作了。我們已經成功的調用了定義在另一個文件里的函數!

示例2

讓我們嘗試在main.rs中調用定義在routes/health_route.rs里的print_health_route函數。

// main.rs
mod config;

fn main() {
  config::print_config();
  println!("main");
}
// routes/health_route.rs
fn print_health_route() {
  println!("health_route");
}

正如我們之前所討論的,我們只能對相同目錄下的my_module.rs或者my_module/mod.rs使用mod關鍵字。
所以為了能夠在main.rs中調用routes/health_route.rs里定義的函數,我們需要做下面的事情:

  • 創建一個名為routes/mod.rs的文件並且在main.rs中聲明routes子模塊
  • routes/mod.rs中聲明health_route子模塊並且使其成為公開(public)的
  • 使health_route.rs里的函數公開(public)
my_project
├── Cargo.toml
└─┬ src
  ├── main.rs
  ├── config.rs
  ├─┬ routes
+ │ ├── mod.rs
  │ ├── health_route.rs
  │ └── user_route.rs
  └─┬ models
    └── user_model.rs
// main.rs
mod config;
mod routes;

fn main() {
+ routes::health_route::print_health_route();
  config::print_config();
  println!("main");
}
// routes/mod.rs
pub mod health_route;
// routes/health_route.rs
fn print_health_route() {
pub fn print_health_route() {
  println!("health_route");
}

下面是模塊樹的樣子:

現在我們可以調用某個目錄下文件里定義的函數了。

示例 3

讓我們嘗試這樣的調用main.rs => routes/user_route.rs => models/user_model.rs(譯者注:這裡是main.rs里調用routes/user_route.rs里的函數,而routes/user_route.rs里的函數又調用了models/user_model.rs里的函數)

// main.rs
mod config;
mod routes;

fn main() {
  routes::health_route::print_health_route();
  config::print_config();
  println!("main");
}
// routes/user_route.rs
fn print_user_route() {
  println!("user_route");
}
// models/user_model.rs
fn print_user_model() {
  println!("user_model");
}

我們想要在main.rs里調用print_user_route函數,而print_user_route函數調用了print_user_model函數

讓我們來進行和之前相同的操作——聲明子模塊,使函數公開並將子模塊添加到mod.rs文件之中。

my_project
├── Cargo.toml
└─┬ src
  ├── main.rs
  ├── config.rs
  ├─┬ routes
  │ ├── mod.rs
  │ ├── health_route.rs
  │ └── user_route.rs
  └─┬ models
+   ├── mod.rs
    └── user_model.rs
// main.rs
mod config;
mod routes;
mod models;

fn main() {
  routes::health_route::print_health_route();
+ routes::user_route::print_user_route();
  config::print_config();
  println!("main");
}
// routes/mod.rs
pub mod health_route;
pub mod user_route;
// routes/user_route.rs
fn print_user_route() {
pub fn print_user_route() {
  println!("user_route");
}
// models/mod.rs
pub mod user_model;
// models/user_model.rs
fn print_user_model() {
pub fn print_user_model() {
  println!("user_model");
}

下面是模塊樹的樣子:


等等,我們還沒有真正地在print_user_route里調用print_user_model!目前為止,我們僅僅在main.rs里調用定義在其他模塊里的函數,在別的文件里調用其他模塊的函數應該怎麼做么?

如果我們看一下我們的模塊樹,print_user_model位於crate::models::user_model。所以為了能在非main.rs的其他文件里使用一個模塊,我們應該按照模塊樹中到達指定模塊所需要的路徑來進行考慮。

// routes/user_route.rs
pub fn print_user_route() {
+ crate::models::user_model::print_user_model();
  println!("user_route");
}

現在我們已經成功地在一個非main.rs的文件里調用了定義在另一個文件里的函數。

super

如果我們的文件組織包含多級目錄,完整的限定名就會變得很長。出於某些原因,我們想要從print_user_route中調用print_health_route。它們分別位於crate::routes::health_routecrate::routes::user_route

我們可以使用完整路徑的限定名crate::routes::health_route::print_health_route();, 但是我們也可以使用一個相對路徑super::health_route::print_health_route();

模塊路徑中的super關鍵字指向父級作用域

pub fn print_user_route() {
  crate::routes::health_route::print_health_route();
  // can also be called using
  super::health_route::print_health_route();

  println!("user_route");
}

use

在上面的例子中,無論是使用完整的限定名還是相對路徑的限定名都很冗長。為了讓限定名變得更短,我們可以使用use關鍵字來給路徑綁定一個新名字或者別名。

use關鍵字用於使模塊路徑更短

pub fn print_user_route() {
  crate::models::user_model::print_user_model();
  println!("user_route");
}

上面的代碼可以重寫為:

use crate::models::user_model::print_user_model;

pub fn print_user_route() {
  print_user_model();
  println!("user_route");
}

除了使用print_user_model這個名字,我們還可以給它起個別名:

use crate::models::user_model::print_user_model as log_user_model;

pub fn print_user_route() {
  log_user_model();
  println!("user_route");
}

外部模塊(External modules)

添加到Cargo.toml里的依賴對於項目內的所有模塊都是可以訪問的。我們不需要顯式地導入或聲明任何東西來使用依賴項。

外部依賴對於項目內的所有模塊都是可以訪問的

例如,比如說我們在項目中添加了rand[1]這個crate。我們可以像下面這樣在代碼里直接使用:

pub fn print_health_route() {
  let random_number: u8 = rand::random();
  println!("{}", random_number);
  println!("health_route");
}

我們也可以使用use來簡化路徑:

use rand::random;

pub fn print_health_route() {
  let random_number: u8 = random();
  println!("{}", random_number);
  println!("health_route");
}

總結

  • 模塊系統是顯式的(譯者注:需要明確的聲明)——不存在和文件系統的1:1映射
  • 我們在一個文件的父級目錄把它聲明為模塊,而不是在文件自身
  • mod關鍵字用於聲明子模塊
  • 我們需要顯式地將函數、結構體等聲明為公開的,這樣它們才可以被其他模塊訪問
  • pub關鍵字把事物聲明為公開的
  • use關鍵字用於簡化(縮短)模塊路徑
  • 我們不需要顯式聲明第三方的模塊

參考資料

[1]

rand: //crates.io/crates/rand


Exit mobile version