1. 程式人生 > >【譯】關於Rust模組的清晰解釋

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

原文連結: http://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: https://crates.io/crates/rand