【Rust】包和模塊管理,以及作用域等問題——Rust語言基礎15

文章目錄

  • 1. 前言
  • 2. 包和 Crate
  • 3. 定義模塊以及模塊之間的關系
  • 4. 作用域問題
    • 4.1. 作用域問題初現
    • 4.2. 解決問題一
    • 4.3. 解決問題二
    • 4.4. super 關鍵字
    • 4.5. 將路徑引入作用域
    • 4.6. as 關鍵字
    • 4.7. pub use 重導出
  • 5. 引入的問題
    • 5.1. 引入一個外部包
    • 5.2. 嵌套路徑來消除大量的 use 行
    • 5.3. 通過 glob 運算符將所有的公有定義引入作用域
  • 6. 將模塊拆分為多文件
  • 7. 小結

1. 前言

經過上一小節無聊又有趣的洗禮相信大家已經提起精神進入下一個內容的學習啦~~

這小節將會了解 Rust 中是以什么樣的形式和工具來組織和管理自己的代碼。我們都知道當代碼量和源文件數量達到一定程度時候,井然有序的組織將變得尤為重要,不然我們的代碼在外人眼里看來就是一坨*,不僅其他人難以閱讀,作為開發者的你回頭看去也是一頭霧水,悔恨自己寫的這是個什么玩意兒。

Rust 中有著嚴格的作用域限制,有這樣的一個模塊系統(the model system)來管理作用域,其中包括:

  • Packages):Cargo 的一個功能,它允許你構建、測試和分享 crate
  • Crates :一個模塊的樹形結構,它形成了庫或二進制項目;
  • 模塊Modules)和 use:允許你控制作用域和路徑的私有性;
  • 路徑path):一個命名例如結構體、函數或模塊等項的方式。

2. 包和 Crate

crateRust 在編譯時最小的代碼單位。如果你用 rustc 而不是 cargo 來編譯一個文件,編譯器還是會將那個文件認作一個 cratecrate 可以包含模塊,模塊可以定義在其他文件,然后和 crate 一起編譯。

crate 有兩種形式:二進制項。二進制項可以被編譯為可執行程序,比如一個命令行程序或者一個 web server。它們必須有一個 main 函數來定義當程序被執行的時候所需要做的事情。目前我們所創建的 crate 都是二進制項。

并沒有 main 函數,它們也不會編譯為可執行程序,庫可以提供一些函數或結構體之類的,就如之前我們所使用過的 rand 庫就為我們提供了隨機數函數。

包(package)是提供一系列功能的一個或者多個 crate。一個包會包含一個 Cargo.toml 文件,闡述如何去構建這些 crateCargo 就是一個包含構建你代碼的二進制項的包。Cargo 也包含這些二進制項所依賴的庫。其他項目也能用 Cargo 庫來實現與 Cargo 命令行程序一樣的邏輯。

包中可以包含至多一個庫 crate(library crate)。包中可以包含任意多個二進制 crate(binary crate),但是必須至少包含一個 crate(無論是庫的還是二進制的)。

讓我們來看看創建包的時候會發生什么。首先,我們輸入命令 cargo new my-project

$ cargo new my-projectCreated binary (application) `my-project` package
$ ls my-project
Cargo.toml
src
$ ls my-project/src
main.rs

Cargo 給我們創建了什么,Cargo 會給我們的包創建一個 Cargo.toml 文件。查看 Cargo.toml 的內容,會發現并沒有提到 src/main.rs,因為 Cargo 默認 src/main.rs 就是一個與包同名的二進制 cratecrate 根。

同樣執行 cargo new --lib my-library 會有同樣的目錄結構生成,不同的是這里的 src/main.rs 變成了 src/lib.rs,并且 src/lib.rs 就是 crate 根。crate 根文件將由 Cargo 傳遞給 rustc 來實際構建庫或者二進制項目。

這里只是包含一個 src/main.rs 的包,意味著它只含有一個名為 my-project 的二進制 crate。如果一個包同時含有 src/main.rssrc/lib.rs,則它有兩個 crate:一個二進制的和一個庫的,且名字都與包相同。通過將文件放在 src/bin 目錄下,一個包可以擁有多個二進制 crate:每個 src/bin 下的文件都會被編譯成一個獨立的二進制 crate

做個簡單的實驗,首先執行 cargo new multiple_bin

$ cargo new multiple_bin
Creating binary (application) `multiple_bin` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
$ cd multiple_bin
$ tree
.
├── Cargo.lock
├── Cargo.toml
└── src└── main.rs

進入 src,創建 bin 目錄,并在其中創建多個 .rs 文件:

$ cd src
$ mkdir bin
$ cp ../main.rs bin01.rs
$ cp ../main.rs bin02.rs
$ cd ../../

編譯該項目:

$ cargo buildCompiling multiple_bin v0.1.0 (/home/im01/Miracle/rust/multiple_bin)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s
$ tree
.
├── Cargo.lock
├── Cargo.toml
├── src
│   ├── bin
│   │   ├── bin01.rs
│   │   └── bin02.rs
│   └── main.rs
└── target├── CACHEDIR.TAG└── debug├── bin01├── bin01.d├── bin02├── bin02.d├── build├── deps│   ├── bin01-54a8a90b566c47ce│   ├── bin01-54a8a90b566c47ce.d│   ├── bin02-057c31f4258a913d│   ├── bin02-057c31f4258a913d.d│   ├── multiple_bin-79155e437d2fa379│   └── multiple_bin-79155e437d2fa379.d├── examples├── incremental......├── multiple_bin└── multiple_bin.d15 directories, 48 files

[注]:這里為了簡潔僅展示了部分主要內容。

target 目錄可以看出這里不僅會為 main.rs 生成了與根同名的 multiple_bin 二進制的 crate,還會為在 bin 目錄下的兩個文件生成對應文件名的二進制 crate

3. 定義模塊以及模塊之間的關系

這里將會涉及到幾個重要的關鍵字:

  • use:將路徑引入作用域;
  • pub:使對應項變為公有性質;
  • as:為同名函數起別名。

在此之前先對幾個概念做以解釋:

  • rust 項目是從 crate 根節點開始檢索代碼:這很好理解,對于一個二進制 crate 的根就是 src/main.rs,而庫則是 src/lib.rs,就類似在 C/C++ 中總是以 main 函數開始;
  • 聲明模塊:在 crate 根文件中用 mod 關鍵字可以聲明一個模塊,如:
	mod xiaomi_car;	// 中國最大的保時捷&法拉利元素融合高性能新能源汽車集團

這便是聲明了一個 xiaomi_car 模塊,而當 mod xiaomi_car 后是一個大括號時,這樣的方式成為內聯,如:

	mod xiaomi_car{fn sale() {}	// 銷售部銷售小米汽車,金牌銷售員:雷將軍}
  • 聲明子模塊:在不是 main.rs 中定義的模塊被稱為子模塊;
  • 公有和私有:一個模塊里的代碼默認對其夫模塊私有。為一個模塊加上 pub 關鍵字即使用 pub mod 來聲明模塊,則表示將該模塊公有化;

看完這些名詞解釋相信大家在大腦中還是一團漿糊,那么接下來將通過一點小實驗逐步讓我們梳理清楚現在正在學習的到底是什么。

當下火出天際的汽車行業的雷小軍的保時米為例來介紹,保時米集團有這樣的部門專門用來營銷宣傳以及銷售汽車被稱為銷售部Sales Department),還有專門用來生產制造的制造部Manufacturing Department)。

好了,現在讓我們來創建一個小米汽車的庫,起名為 xiaomi_car

$ cargo new --lib xiaomi_carCreating library `xiaomi_car` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

編輯 src/lib.rs 文件添加如下內容:

mod sales_department {// 車輛售前服務mod pre_sales_service {// 車型咨詢fn model_consultation() {}// 制定購車方案fn purchase_plan_options() {}}// 車輛售后服務mod after_sales_service {// 車輛保養服務fn vehicle_maintenance_services() {}// 維修服務fn repair_services() {}}
}

這里我們用關鍵字 mod 定義了一個名為 sales_department 的模塊,接著花括號內為該模塊的主體部分。在模塊內仍然可以指定其它模塊,正如這里的 pre_sales_serviceafter_sales_service 模塊是屬于 sales_department 的子模塊,相應的 sales_department 是他們的父模塊。而模塊內還可以定義一些其它的各種類型,如結構體、枚舉、常量、函數等。

通過以模塊的方式將相關的代碼定義再一起,這樣會更有組織性的管理程序,以 src/main.rssrc/lib.rs 作為 crate 根,構成了一整個模塊樹module tree)。

上面的代碼就展示如下這樣結構的設備樹。

crate└── sales_department ├── pre_sales_service │   ├── model_consultation│   └── purchase_plan_options└── after_sales_service ├── vehicle_maintenance_services└── repair_services

rust 里仍然是借用家庭關系來描述模塊之間的關系,在同一個模塊中定義的子模塊互為兄弟模塊(siblings module),而包含著子模塊的模塊稱為他們的父模塊(parent module)。注意,整個模塊樹都植根于名為 crate 的隱式模塊下。這樣一來我們就可以更加清晰的設計和組織我們的代碼。

4. 作用域問題

4.1. 作用域問題初現

上面我們了解到模塊之間的結構可以被抽象成為樹狀結構,那么不同層之間的模塊是否能夠相互調用呢?(既然已經這么問了,那么一定是不可以咯~)總而言是,先試試看吧。

// 保時米的銷售部門
mod sales_department {// 車輛售前服務mod pre_sales_service {// 車型咨詢fn model_consultation() {}// 制定購車方案fn purchase_plan_options() {}// 添加到生產訂單fn add_to_production_order() {}}// 車輛售后服務mod after_sales_service {// 車輛保養服務fn vehicle_maintenance_services() {}// 維修服務fn repair_services() {}}
}// 保時米的生產制造部門
mod manufacturing_department {// 生產計劃mod production_planning {// 制定生產計劃fn specify_production_plan() {// 添加到生產訂單add_to_production_order();}}// 總裝車間mod final_assembly_workshop {}
}

讓我們就這樣編譯看下能不能通過:

im01@Ubuntu:xiaomi_car$ cargo buildCompiling xiaomi_car v0.1.0 (/home/im01/Miracle/rust/xiaomi_car)
error[E0425]: cannot find function `add_to_production_order` in this scope--> src/lib.rs:23:13|
23 |             add_to_production_order();|             ^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope|
note: function `crate::sales_department::pre_sales_service::add_to_production_order` exists but is inaccessible--> src/lib.rs:7:9|
7  |         fn add_to_production_order() {}|         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not accessibleFor more information about this error, try `rustc --explain E0425`.
error: could not compile `xiaomi_car` (lib) due to 1 previous error

哦~哦吼吼~,不出意外的出意外了,從錯誤信息中可以看到,沒有在當前范圍內找到 add_to_production_order 這個函數,再看到下面的提示告訴我們說,有一個同名的函數存在,但是不可以訪問。注意一下,這里有兩點問題:

  • 第一:我們函數調用的方式不對,因為調用了一個不存在的函數;
  • 第二:即便我們去調用這個存在的函數,同樣存在不可訪問的問題。

4.2. 解決問題一

根據提示我們可以解決第一個問題,修改上面代碼:

mod sales_department {mod pre_sales_service {//-------------snip----------------fn add_to_production_order() {}//-------------snip----------------}
}mod manufacturing_department {mod production_planning {fn specify_production_plan() {//add_to_production_order();// 絕對路徑crate::sales_department::pre_sales_service::add_to_production_order();}}mod final_assembly_workshop {}
}pub fn sales_announcement() {// 相對路徑sales_department::pre_sales_service::add_to_production_order();
}

[注]:此處為了突出主要內容,省略了部分無關代碼。這里為了說明相對路徑問題額外添加 sales_announcement 函數。

這里引入兩個概念:

  • 絕對路徑:crate 開始,即從根開始按照樹狀結構索引出來的,每一層之間用 :: 隔開;
  • 相對路徑: 從當前位置出發,即從同一層的模塊位置出發索引,同樣每層之間用 :: 隔開。

好了,這里我們編譯一下,看看會出現什么問題:

im01@Ubuntu:xiaomi_car$ cargo buildCompiling xiaomi_car v0.1.0 (/home/im01/Miracle/rust/xiaomi_car)
error[E0603]: module `pre_sales_service` is private--> src/lib.rs:25:38|
25 |             crate::sales_department::pre_sales_service::add_to_production_order();|                                      ^^^^^^^^^^^^^^^^^  ----------------------- function `add_to_production_order` is not publicly re-exported|                                      ||                                      private module|
note: the module `pre_sales_service` is defined here--> src/lib.rs:2:5|
2  |     mod pre_sales_service {|     ^^^^^^^^^^^^^^^^^^^^^error[E0603]: module `pre_sales_service` is private--> src/lib.rs:37:23|
37 |     sales_department::pre_sales_service::add_to_production_order();|                       ^^^^^^^^^^^^^^^^^  ----------------------- function `add_to_production_order` is not publicly re-exported|                       ||                       private module|
note: the module `pre_sales_service` is defined here--> src/lib.rs:2:5|
2  |     mod pre_sales_service {|     ^^^^^^^^^^^^^^^^^^^^^For more information about this error, try `rustc --explain E0603`.
error: could not compile `xiaomi_car` (lib) due to 2 previous errors

錯誤信息中告訴我們 pre_sales_service 是一個私有模塊,被 sales_department 模塊私有,因此外部無法訪問(這里所指的外部是 pre_sales_service 同層之外)。

4.3. 解決問題二

此時的整個模塊樹看起來是這樣的。

crate├── sales_department │   ├── pre_sales_service │   │   ├── model_consultation│   │   ├── purchase_plan_options│   │   └── add_to_production_order│   └── after_sales_service │       ├── vehicle_maintenance_services│       └── repair_services│└── manufacturing_department ├── production_planning │   └── specify_production_plan└── final_assembly_workshop 

要想使得外部也可以訪問,這里就需要使用到關鍵字 pub

mod sales_department {pub mod pre_sales_service {fn model_consultation() {}fn purchase_plan_options() {}fn add_to_production_order() {}}mod after_sales_service {fn vehicle_maintenance_services() {}fn repair_services() {}}
}//---------------snip---------------------------

那么只在 pre_sales_service 前加上 pub 是否就可以了呢?編譯試試看:

im01@Ubuntu:xiaomi_car$ cargo buildCompiling xiaomi_car v0.1.0 (/home/im01/Miracle/rust/xiaomi_car)
error[E0603]: function `add_to_production_order` is private--> src/lib.rs:25:57|
25 |             crate::sales_department::pre_sales_service::add_to_production_order();|                                                         ^^^^^^^^^^^^^^^^^^^^^^^ private function|
note: the function `add_to_production_order` is defined here--> src/lib.rs:7:9|
7  |         fn add_to_production_order() {}|         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^error[E0603]: function `add_to_production_order` is private--> src/lib.rs:37:42|
37 |     sales_department::pre_sales_service::add_to_production_order();|                                          ^^^^^^^^^^^^^^^^^^^^^^^ private function|
note: the function `add_to_production_order` is defined here--> src/lib.rs:7:9|
7  |         fn add_to_production_order() {}|         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^For more information about this error, try `rustc --explain E0603`.
error: could not compile `xiaomi_car` (lib) due to 2 previous errors

正如之前所說,外部無法訪問指的外部是同層之外。因此 add_to_production_order 函數前也需要加上 pub

mod sales_department {pub mod pre_sales_service {fn model_consultation() {}fn purchase_plan_options() {}pub fn add_to_production_order() {}}mod after_sales_service {fn vehicle_maintenance_services() {}fn repair_services() {}}
}//---------------snip---------------------------

這樣一來編譯就沒問題了。到這里相信大家已經粗略的感受到了在 rust 中作用域的概念。

4.4. super 關鍵字

這里換一種方式去調用 add_to_production_order

mod sales_department {pub mod pre_sales_service {fn model_consultation() {}fn purchase_plan_options() {}pub fn add_to_production_order() {}}mod after_sales_service {fn vehicle_maintenance_services() {}fn repair_services() {}}
}mod manufacturing_department {mod production_planning {fn specify_production_plan() {//add_to_production_order();crate::sales_department::pre_sales_service::add_to_production_order();// 等同于上一行super::super::sales_department::pre_sales_service::add_to_production_order();}}mod final_assembly_workshop {}
}pub fn sales_announcement() {sales_department::pre_sales_service::add_to_production_order();
}

此處便用到了 super 關鍵字,其作用想必各位從形勢中也窺竊出一二來,沒錯 super 關鍵字的作用類似與 Linux 文件系統中的 .. 語法——到上一級目錄,而相應的這里則是到上一級模塊層。

為什么 rust 會多此一舉的設計這樣的關鍵字,原因很簡單,當全文都在使用絕對路徑,這樣沒錯,但會顯得代碼冗長。而全文又使用相對路徑,則會導致邏輯看起來混亂,難以閱讀,一旦代碼做過改動尤其是移動之后,將會帶來相應的錯誤,而定位起來也較為不便,那么有了 super 的引入之后,我們很清楚的知道模塊之間的關系,當代碼整體移動時,也不必擔心路徑不對而需要修改調用路徑。就是這樣。

4.5. 將路徑引入作用域

上面兩個問題的解決方法雖然有效,但是仍然讓代碼顯得冗長,這里將會介紹一個關鍵字 use 直接將需要的路徑引入當前作用域,可以極大的簡化代碼的長度。

mod sales_department {pub mod pre_sales_service {fn model_consultation() {}fn purchase_plan_options() {}pub fn add_to_production_order() {}}
}mod manufacturing_department {mod production_planning {// 使用 use 關鍵字use crate::sales_department::pre_sales_service;fn specify_production_plan() {//add_to_production_order();pre_sales_service::add_to_production_order();}}
}

在當前作用域中增加 use 和路徑類似于在文件系統中創建軟連接(符號連接,symbolic link)。需要注意的是,use 只能對當前作用域范圍內有效,若上面代碼改為如下這樣,則會無法使用到 use 引入進來的路徑:

mod sales_department {pub mod pre_sales_service {fn model_consultation() {}fn purchase_plan_options() {}pub fn add_to_production_order() {}}
}// 使用 use 關鍵字
use crate::sales_department::pre_sales_service;mod manufacturing_department {mod production_planning {fn specify_production_plan() {//add_to_production_order();pre_sales_service::add_to_production_order();}}
}

事實上 use 也可以直接將其全部路徑都引入,像這樣:

mod sales_department {pub mod pre_sales_service {pub fn add_to_production_order() {}}
}mod manufacturing_department {mod production_planning {use crate::sales_department::pre_sales_service::add_to_production_order;fn specify_production_plan() {add_to_production_order();//pre_sales_service::add_to_production_order();}}
}

直接引入 add_to_production_order 函數的完整路徑,這樣的作法是允許的,而常常的做法會像之前一樣,引入到其上一級,這樣編寫的代碼會很明確看出來該函數是別的模塊,而非當前模塊。

4.6. as 關鍵字

這樣引入完整路徑會有什么問題嗎?假設有兩個同名函數不同模塊,被同時引入呢?

答案是,rust 編譯器將會告訴你這樣的操作是不允許的。

那的確出現這樣的情況怎么做?這時候 rust 提供了我們另外一個關鍵字 as,他可以為引入的變量或函數起別名,就像這樣:

mod sales_department {pub mod pre_sales_service {pub fn test_func() {}}pub mod after_sales_service {pub fn test_func() {}}
}use crate::sales_department::pre_sales_service::test_func as pre_test_func;
use crate::sales_department::after_sales_service::test_func as after_test_func;
pub fn sales_announcement() {pre_test_func();after_test_func();
}

使用 as 關鍵字讓兩個本來會使用沖突的函數同時可以引入當前作用域。

4.7. pub use 重導出

mod sales_department {pub mod pre_sales_service {pub fn add_to_production_order() {}}
}pub use crate::sales_department::pre_sales_service;pub fn sales_announcement() {pre_sales_service::add_to_production_order();
}

這段代碼與之前有所不同,這里引入路徑使用了 pub use 而非 use,這樣的作用是為了讓外部代碼也可以用這樣的路徑。

使用 use 關鍵字
在使用 use 引入路徑時,外部代碼調用 add_to_production_order 需要指定其完整路徑xiaomi_car::sales_department::pre_sales_service::add_to_production_order() 才能夠調用該函數;

使用 pub use 關鍵字
當使用 pub use 引入時,外部代碼則可以通過 xiaomi_car::pre_sales_service::add_to_production_order()來調用該函數,仔細觀察二者的區別,這樣可以省略中間的一大堆具體路徑,何樂而不為呢。

5. 引入的問題

5.1. 引入一個外部包

在之前我們編寫了一個猜猜看游戲。那個項目使用了一個外部包,rand,來生成隨機數。為了在項目中使用 rand,在 Cargo.toml 中加入了如下行:

rand = "0.8.5"

Cargo.toml 中加入 rand 依賴告訴了 Cargo 要從 crates.io 下載 rand 和其依賴,并使其可在項目代碼中使用。

接著,為了將 rand 定義引入項目包的作用域,我們加入一行 use 起始的包名,它以 rand 包名開頭并列出了需要引入作用域的項。回憶一下之前的 “生成一個隨機數” 部分,我們曾將 Rng trait 引入作用域并調用了 rand::thread_rng 函數:

use rand::Rng;fn main() {let secret_number = rand::thread_rng().gen_range(1..=100);
}

crates.io 上有很多 Rust 社區成員發布的包,將其引入你自己的項目都需要一道相同的步驟:在 Cargo.toml 列出它們并通過 use 將其中定義的項引入項目包的作用域中。

注意 std 標準庫對于你的包來說也是外部 crate。因為標準庫隨 Rust 語言一同分發,無需修改 Cargo.toml 來引入 std,不過需要通過 use 將標準庫中定義的項引入項目包的作用域中來引用它們,比如我們使用的 HashMap

use std::collections::HashMap;

這是一個以標準庫 cratestd 開頭的絕對路徑。

5.2. 嵌套路徑來消除大量的 use 行

當需要引入很多定義于相同包或相同模塊的項時,為每一項單獨列出一行會占用源碼很大的空間。例如猜猜看代碼中有兩行 use 語句都從 std 引入項到作用域:

// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--

相反,我們可以使用嵌套路徑將相同的項在一行中引入作用域。這么做需要指定路徑的相同部分,接著是兩個冒號,接著是大括號中的各自不同的路徑部分:

// --snip--
use std::{cmp::Ordering, io};
// --snip--

在較大的程序中,使用嵌套路徑從相同包或模塊中引入很多項,可以顯著減少所需的獨立 use 語句的數量!

我們可以在路徑的任何層級使用嵌套路徑,這在組合兩個共享子路徑的 use 語句時非常有用。例如有兩個 use 語句:一個將 std::io 引入作用域,另一個將 std::io::Write 引入作用域:

use std::io;
use std::io::Write;

兩個路徑的相同部分是 std::io,這正是第一個路徑。為了在一行 use 語句中引入這兩個路徑,可以在嵌套路徑中使用 self

use std::io::{self, Write};

這一行代碼便將 std::iostd::io::Write 同時引入作用域。

5.3. 通過 glob 運算符將所有的公有定義引入作用域

如果希望將一個路徑下所有公有項引入作用域,可以指定路徑后跟 *——glob 運算符:

use std::collections::*;

這個 use 語句將 std::collections 中定義的所有公有項引入當前作用域。使用 glob 運算符時請多加小心!Glob 會使得我們難以推導作用域中有什么名稱和它們是在何處定義的。

glob 運算符經常用于測試模塊 tests 中,這時會將所有內容引入作用域。

6. 將模塊拆分為多文件

到目前為止,所有的例子都在一個文件中定義多個模塊。當模塊變得更大時,你可能想要將它們的定義移動到單獨的文件中,從而使代碼更容易閱讀。

為了避免產生歧義,這里貼出筆者希望拆分的原本代碼:

mod sales_department {pub mod pre_sales_service {fn model_consultation() {}fn purchase_plan_options() {}pub fn add_to_production_order() {}}pub mod after_sales_service {fn vehicle_maintenance_services() {}fn repair_services() {}}
}mod manufacturing_department {mod production_planning {fn specify_production_plan() {crate::sales_department::pre_sales_service::add_to_production_order();}}mod final_assembly_workshop {}
}pub fn sales_announcement() {
sales_department::pre_sales_service::add_to_production_order();
}

首先嘗試將 sales_departmentmanufacturing_department 模塊拆分出去,首先在 src 目錄下創建 sales_department.rs 文件,為其添加如下內容:

pub mod pre_sales_service {fn model_consultation() {}fn purchase_plan_options() {}pub fn add_to_production_order() {}
}pub mod after_sales_service {fn vehicle_maintenance_services() {}fn repair_services() {}
}

[注]:這里是 src/sales_department.rs 文件

然后創建 manufacturing_department.rs 文件,為其添加如下內容:

mod production_planning {fn specify_production_plan() {crate::sales_department::pre_sales_service::add_to_production_order();}
}mod final_assembly_workshop {
}

[注]:這里是 src/manufacturing_department.rs 文件

然后再修改 src/lib.rs 文件內容:

mod sales_departmentmod manufacturing_departmentpub fn sales_announcement() {
sales_department::pre_sales_service::add_to_production_order();
}

這樣就完成了這兩個模塊的拆分。因為編譯器找到了 crate 根中名叫 sales_department 的模塊聲明,它就知道去搜尋 src/sales_department.rs 這個文件。

那如果還想繼續拆分呢?要怎么做,其實道理相同,下面筆者展示將 sales_department 模塊繼續拆分成多個文件。

首先在 src 目錄下創建 sales_department 目錄,再進入 sales_department 目錄,分別創建文件 pre_sales_service.rsafter_sales_service.rs,并為其添加如下內容:

fn model_consultation() {}fn purchase_plan_options() {}pub fn add_to_production_order() {}

[注]:這里是 src/sales_department/pre_sales_service.rs 文件

fn vehicle_maintenance_services() {}fn repair_services() {}

[注]:這里是 src/sales_department/after_sales_service.rs 文件

這樣一來就將該模塊繼續拆分為更多的文件,這樣拆分完后的文件目錄結構如下。這個目錄結構是不是很像我們的模塊樹。如果完全拆開,那么這就是模塊樹。

im01@Ubuntu:xiaomi_car$ tree
.
├── Cargo.lock
├── Cargo.toml
└──  src├── lib.rs├── manufacturing_department├── manufacturing_department.rs├── sales_department│   ├── after_sales_service.rs│   └── pre_sales_service.rs└── sales_department.rs

各位應該看到, 這里筆者提前創建了一個 manufacturing_department 目錄,各位同學可以自己嘗試將 manufacturing_department 模塊繼續拆分。這個技巧讓你可以在模塊代碼增長時,將它們移動到新文件中。

注意我們只需在模塊樹中的某處使用一次 mod 聲明就可以加載這個文件。一旦編譯器知道了這個文件是項目的一部分(并且通過 mod 語句的位置知道了代碼在模塊樹中的位置),項目中的其他文件應該使用其所聲明的位置的路徑來引用那個文件的代碼,這在“引用模塊項目的路徑”部分有講到。換句話說,mod 不是你可能會在其他編程語言中看到的 "include" 操作。

7. 小結

Rust 提供了將包分成多個 crate,將 crate 分成模塊,以及通過指定絕對或相對路徑從一個模塊引用另一個模塊中定義的項的方式。你可以通過使用 use 語句將路徑引入作用域,這樣在多次使用時可以使用更短的路徑。模塊定義的代碼默認是私有的,不過可以選擇增加 pub 關鍵字使其定義變為公有。

這一小節的文章很長,筆者寫的自認為也太過啰嗦,能夠堅持看完的你真的很厲害,請收下筆者贈與的小紅花🌸,萬分感激您的閱讀,有任何疑問或問題還請不吝賜教~

下一篇《【Rust】集合的使用——Rust語言基礎16》


覺得這篇文章對你有幫助的話,就留下一個贊吧v*
請尊重作者,轉載還請注明出處!感謝配合~
[作者]: Imagine Miracle
[版權]: 本作品采用知識共享署名-非商業性-相同方式共享 4.0 國際許可協議進行許可。
[本文鏈接]: https://blog.csdn.net/qq_36393978/article/details/146339937

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/898600.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/898600.shtml
英文地址,請注明出處:http://en.pswp.cn/news/898600.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

微服務架構中的API網關:Spring Cloud與Kong/Traefik等方案對比

微服務架構中的API網關:Spring Cloud與Kong/Traefik等方案對比 一、API 網關的概念二、API 網關的主要功能2.1 統一入口與路由轉發2.2 安全與權限控制2.3 流量管理與容錯2.4 API 管理與聚合2.5 監控與日志2.5 協議轉換與適配2.6 控制平面與配置管理 三、API 網關選型…

NewStar CTF web wp

文章目錄 week1headach3會贏嗎智械危機謝謝皮蛋PangBai 過家家(1) week3include meblindsql1臭皮的計算機臭皮踩踩背這照片是你嗎 week4Pangbai過家家四blindsql2chocolateezcmsssezpollute隱藏的密碼 weeek5pangbai過家家(5)redissqlshell臭皮吹泡泡臭皮…

Linux驅動開發-①中斷②阻塞、非阻塞IO和異步通知

Linux驅動開發-①中斷②阻塞、非阻塞IO和異步通知 一,中斷1.中斷的流程2.上半部和下半部2.1上半部2.2下半部2.2.1 tasklet2.2.2 工作隊列 3.按鍵延時消抖中斷程序 二,阻塞和非阻塞IO和異步通知1.阻塞IO1.1 常見結構11.2 常見結構2 2.非阻塞IO2.1 驅動結構…

Docker和Dify學習筆記

文章目錄 1 docker學習1.1 基本命令使用1.1.1 docker ps查看當前正在運行的鏡像1.1.2 docker stop停止容器1.1.3 docker compose容器編排1.1.4 docker網絡[1] 進入到容器里面敲命令[2] docker network ls[3] brige網絡模式下容器訪問宿主機的方式 2 Dify的安裝和基礎使用2.1 下…

高并發庫存系統是否適合使用 ORM(Hibernate / MyBatis)

在設計高并發的庫存管理系統時,數據層的選擇至關重要。許多企業開發中習慣使用 ORM(如 Hibernate、MyBatis)來簡化數據庫訪問,但在高并發、高吞吐的場景下,ORM 的適用性往往成為爭議焦點。本文將探討高并發庫存系統是否…

Web爬蟲利器FireCrawl:全方位助力AI訓練與高效數據抓取。本地部署方式

開源地址:https://github.com/mendableai/firecrawl 01、FireCrawl 項目簡介 Firecrawl 是一款開源、優秀、尖端的 AI 爬蟲工具,專門從事 Web 數據提取,并將其轉換為 Markdown 格式或者其他結構化數據。 Firecrawl 還特別上線了一個新的功…

探秘Transformer系列之(16)--- 資源占用

探秘Transformer系列之(16)— 資源占用 文章目錄 探秘Transformer系列之(16)--- 資源占用0x00 概述0x01 背景知識1.1 數據類型1.2 進制&換算數字進制存儲度量換算 1.3 參數顯存占用有參數的層無參數的層所需資源 1.4 計算量 0…

jaeger安裝和簡單使用

文章目錄 jaeger安裝和使用什么是jaegerjaeger安裝 jaeger安裝和使用 什么是jaeger 官網:https://www.jaegertracing.io/ Jaeger 是一個分布式追蹤系統。Jaeger的靈感來自 Dapper 和 OpenZipkin,是一個由 Uber 創建并捐贈給 云原生計算基金會&#xf…

【Mybatis-plus】在mybatis-plus中 if test標簽如何判斷 list不為空

博主介紹:?全網粉絲22W,CSDN博客專家、Java領域優質創作者,掘金/華為云/阿里云/InfoQ等平臺優質作者、專注于Java技術領域? 技術范圍:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大數據、物…

FRP在物聯網設備中的穿透方案

物聯網設備常位于NAT后,FRP為其提供穩定穿透鏈路。 配置要點 輕量化部署:使用ARM版本FRP客戶端,適配樹莓派等設備9。 自啟動腳本:通過systemd或crontab實現設備重啟后自動連接26。 低功耗優化:調整心跳間隔&#xf…

【遞歸,搜索與回溯算法篇】- 名詞解釋

一. 遞歸 1. 什么是遞歸? 定義: 函數自己調用自己的情況關鍵點: ?終止條件: 必須明確遞歸出口,避免無限遞歸 ?子問題拆分: 問題需能分解成結構相同的更小的子問題缺點: ?棧溢出風險&#x…

條件變量,鎖,共享數據的關系

條件變量、共享數據和鎖之間的三方耦合關系源于多線程環境下對資源訪問的同步需求。以下是關鍵點分析: 條件變量中通常會對共享數據進行判斷和處理,如果不加鎖就會出現數據競爭的問題,所以并不是條件變量要跟鎖一起使用,而是上鎖為…

大屏技術匯集【目錄】

Cesium 自從首次發布以來,經歷了多個版本的迭代和更新,每個版本都帶來了性能改進、新功能添加以及對現有功能的優化。以下是 Cesium 一些重要版本及其主要特點: 主要版本概述 Cesium 1.0 (2012年) 初始版本發布,確立了Cesium作為…

圖解AUTOSAR_CP_EEPROM_Abstraction

AUTOSAR EEPROM抽象模塊詳細說明 基于AUTOSAR標準的EEPROM抽象層技術解析 目錄 1. 概述 1.1 核心功能1.2 模塊地位2. 架構概覽 2.1 架構層次2.2 模塊交互3. 配置結構 3.1 主要配置容器3.2 關鍵配置參數4. 狀態管理 4.1 基本狀態4.2 狀態轉換5. 接口設計 5.1 主要接口分類5.2 接…

C++相關基礎概念之入門講解(下)

1. 引用 ? int main() {const int a10;int& aaa;aa;cout<<aa<<endl; } 引用 不是新定義一個變量&#xff0c;而 是給已存在變量取了一個別名 &#xff0c;編譯器不會為引用變量開辟內存空 間&#xff0c;它和它引用的變量 共用同一塊內存空間&#xff08;初…

注意力機制,本質上是在做什么?

本文以自注意機制為例&#xff0c;輸入一個4*4的矩陣 如下&#xff1a; input_datatorch.tensor([[1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,16] ],dtypetorch.float) 得到Q和K的轉置如下。 此時&#xff0c;計算QK^T ,得到如下結果 第一行第一個位置就是第一條樣本和第…

記一次wsl2+docker無法運行的經歷

前情提要 由于某個大創項目的需要和對貓娘機器人的迫切渴求&#xff08;bushi 需要在電腦里面安裝docker desktop。由于電腦里面安裝了wsl2環境 因此決定使用wsl2dockerdesktop的方式配置docker 遇到的問題 在像往常一樣安裝docker desktop并且啟動時 提示錯誤&#xff1a; …

PageHelper插件依賴引入不報錯,但用不了

情況: 父模塊pom. Xml 引入1. 4. 0以上版本的pagehelper-spring-boot-starter。 要用到插件的子模塊&#xff0c;去掉版本號&#xff0c;引入和父模塊一樣的依賴。 引入成功&#xff0c;沒有報錯&#xff0c;但是打開右邊的maven里面沒有找到PageHelper插件。 終端清空并重…

Windows搭建免翻墻的BatteryHistorian

文章參考 GitCode - 全球開發者的開源社區,開源代碼托管平臺 免翻墻的BatteryHistorian主要原理&#xff1a;修改go源碼 1.安裝Java環境 1.點擊下載 Java JDK&#xff0c;并安裝,一路next 2.java -version 檢驗是否安裝成功 2.安裝Git工具 1、點擊下載 Git&#xff0c;并…

項目中pnpm版本和全局pnpm版本不一致

項目中pnpm版本和全局pnpm版本不一致 檢查package.json中&#xff0c;是否存在"packageManager": “pnpm8.6.10”&#xff0c;限制了pnpm的版本。