[Rust] 三言兩語理解 Module Syste

Rust 的 module system 容易讓人摸不着頭腦。The book

寫了很多的內容,但是不適合有一定編碼經驗的人。

因爲裏面的內容更多的是強調 module 的好處,解決了什麼問題,並沒有對比 Rust 的 module 系統與其他的編程語言的區別。

本文記錄 Rust module 系統讓人困惑的三個地方,方便與我有同樣困惑的人,也方便我日後參考。

歡迎閱讀我其他關於 Rust 的文章,

刻舟求劍與 Pin

Rust: PhantomData,#may_dangle 和 Drop Check 真真假假

Module System

一個大問題總會由許多小問題組成。module system 是爲了定義清楚各個小問題的邊界。

這樣更容易和更方便的管理問題。而大問題的解法,就是把小問題的解法組合起來。

下面是 Rust Module System 讓人困惑的三個地方。

  1. project, package, crate, module 的區別

project,package, crate, module 這些概念感覺相似。

它們的關係是,

  1. package 等同於 projectcargo new  會創建一個 project。

  2. 一個 package/project 可以包含多個 binary crates 和 0 個或者 1 library binary。

  3. 一個 crate 可以包含多個 module。

當 proejct 包含 lib.rs,那 lib.rs 是 library crate。bin 文件夾包含的文件就是 binary crates,可以由 cargo new --lib project_name 生成。這個 library 的名字是 project 的名字。

當 project 只包含 main.rs,那麼它就是一個只包含 binary crate 的 project,可以通過命令 cargo new --bin project_name 生成

可以認爲 package 就是一個 project,一個 crate 就是一個暴露給外界的邏輯單元,一個 module 就是一個小問題的解法。

比如下面的 project 結構

client.rs 和 server.rs 可以直接使用這個 library crate。可以認爲 bin 文件夾裏面是單獨的 crate,它們默認依賴了這個 library crate。

  1. module tree 是怎麼生成的

project 裏面的 module tree 必須顯示地指明。

而且一開始的時候,編譯器看到的 project 的 module tree 僅僅含有 main.rs 或者 lib.rs(下圖右邊的黃色框框),又叫做 crate root。

代碼 crate::foo 的 crate 指代的就是這個 crate root。

上圖左邊的目錄樹,大部分人看到文件系統已經有結構並且組織好,會下意識地認爲編譯器也會看到這個文件目錄結構。但是實際上,編譯器看到的 project 僅僅包含了 main.rs。

這一點 Rust 跟其他語言區別很大。比如在 Java 裏面,你可以直接 import 某個目錄裏面的 class。在 Rust,如果直接 use 某個目錄的 module 是編譯通不過的。報錯如下

error[E0432]: unresolved import `http_server::log`
 --> src/bin/main.rs:3:5
  |
3 | use http_server::log;
  |     ^^^^^^^^^^^^^^^^ no `log` in the root

因爲你還沒有將 log 添加到 crate root 裏面。也就是要顯示地指明 module tree 的結構。這也就是我們經常在 main.rs/lib.rs 裏面看到許多 mod xxx 的原因。

比如下面的代碼,

https://github.com/Celthi/rsnova/blob/master/src/lib.rs#L20

它們的存在就是爲了將 project 裏面的 modules 加到這個 crate 裏面。

比如在 main.rs 裏面看到 mod channel,就是將 module channel 加進 crate 的 module tree 來。

這點讓我明白了 Rust 的”be explicit“原則無處不在。

綜上所述,Rust 的 module tree 的一種樣子是,

可能有人會問爲什麼模塊已經存在文件系統裏面了,還要顯示通過 mod foo 將模塊添加到 Rust 裏面。

可以這麼理解,Rust 在生成程序的時候,是由 crate root 去將代碼包含進來。

這也就是爲什麼叫 crate root 的原因——它是根節點,從這個根節點,一點點地添加模塊進來,生成整個 package。

  1. 父模塊和子模塊的包含方法

有時候一個 folder 裏面有個 mod.rs,有時候沒有。這是因爲 mod foo 的作用是告訴編譯器找尋 foo.rs 或者 foo/mod.rs,並且將找尋到的文件內容作爲 module foo 的內容。

你可以選擇你喜歡的方式使用 foo.rs 或者 foo/mod.rs。

使用文件夾 foo 存儲 modules 的時候,我們可以創建一個跟文件夾同名的 foo.rs 來添加文件夾裏面的 modules,也可以在文件夾 foo 裏面用 mod.rs 添加對應的 modules。

所以,mod foo 是將 foo 模塊添加到 project 的 module tree。

use foo; 是將一個模塊加進當前的 scope。

而./foo.rs 或者./foo/mod.rs, 或者 mod foo {...} 定義模塊,它們三者是等效的。

其中./foo.rs 可以使用文件夾 foo 用於存放子模塊。./foo/mod.rs 已經有文件夾 foo,它也可以用於存放子模塊。

所以./foo/mod.rs 等效於./foo.rs 加上./foo / 文件夾。

我個人喜歡使用./foo.rs 加上文件夾 foo 來組織子模塊。

module 的可見性,並不讓人困惑,所以本文不提。workspace 也比較容易理解,所以略過。

總結


Rust module system 提煉爲下面三點——

參考文獻


The Edition Guide

Mentally Modelling ModulesRust modules confusion when there is main.rs and lib.rs

本文第一章圖片來源於 Clear explanation of Rust’s module system。

The confusion around Rust's modules reminds me of the different ways that people...

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/IAqHE-5YHJhwERmqB1YmUg