Cargo 作為 Rust 的核心建置工具和套件管理器,不僅簡化了依賴管理,也提供了強大的檔案生成機制。透過 cargo doc 命令,開發者能快速產生 HTML 格式的專案檔案,並利用 Rust 的檔案註解功能,撰寫清晰易懂的程式碼說明。此外,Cargo 的測試框架能自動驗證檔案中的程式碼範例,確保檔案與程式碼的一致性。Rust 的模組系統則提供了程式碼組織和名稱空間管理的機制,方便開發者構建結構清晰、易於維護的大型專案。更進一步地,工作空間功能允許多個相關專案分享依賴和組態,簡化了多專案管理的複雜性。對於需要與其他語言或系統整合的場景,Cargo 的自訂建置指令碼功能提供了高度的靈活性。最後,Rust 的安全性與效能優勢,使其成為嵌入式系統開發的理想選擇。
使用 Cargo 管理 Rust 專案與檔案生成
Cargo 是 Rust 的套件管理工具,不僅能管理專案的相依性,還能生成檔案。本章節將介紹如何使用 Cargo 生成檔案,以及 Rust 檔案的撰寫方式。
生成檔案
首先,使用 cargo doc 命令可以生成專案的檔案。執行此命令後,Cargo 會在 target/doc 目錄下生成 HTML 格式的檔案。
$ cargo doc
Documenting rustdoc-example v0.1.0
(/Users/brenden/dev/code-like-a-pro-in-rust/code/c2/2.8/rustdoc-example)
Finished dev [unoptimized + debuginfo] target(s) in 0.89s
編寫檔案
Rust 檔案使用 CommonMark 語法撰寫,這是一種 Markdown 的子集。要為函式或模組新增檔案,可以使用 /// 或 //! 註解。
//! # rustdoc-example
//!
//! 一個簡單的專案,展示如何使用 rustdoc 與函式 [`mult`]。
#![warn(missing_docs)]
/// 傳回 `a` 和 `b` 的乘積。
pub fn mult(a: i32, b: i32) -> i32 {
a * b
}
在上述範例中,//! 用於撰寫 crate 或模組層級的檔案,而 /// 用於函式或型別的檔案。#![warn(missing_docs)] 屬性會讓編譯器對缺少檔案的公開函式或型別發出警告。
內容解密:
//!註解:用於 crate 或模組層級的檔案,通常放在檔案開頭,描述整個 crate 或模組的功能。#![warn(missing_docs)]:編譯器屬性,用於檢查公開 API 是否有檔案,若無則發出警告。///註解:用於函式或型別的檔案,描述其功能與用法。
檔案中的程式碼範例
Rust 檔案的一大特點是,可以在檔案中包含程式碼範例,並且這些範例會被編譯和執行,作為整合測試的一部分。這樣可以確保檔案的範例是正確且可執行的。
//! # 範例
//!
//!
---
--
//! use rustdoc_example::mult;
//! assert_eq!(mult(10, 10), 100);
//!
---
--
執行 cargo test 時,這些範例會被測試,確保其正確性。
$ cargo test
Compiling rustdoc-example v0.1.0
(/Users/brenden/dev/code-like-a-pro-in-rust/code/c2/2.8/rustdoc-example)
Finished test [unoptimized + debuginfo] target(s) in 0.42s
Running target/debug/deps/rustdoc_example-bec4912aee60500b
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out;
finished in 0.00s
Doc-tests rustdoc-example
running 1 test
test src/lib.rs - (line 7) ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out;
finished in 0.23s
內容解密:
- 檔案中的程式碼範例:使用 Markdown 語法標記範例程式碼,並確保其正確性和可執行性。
cargo test的作用:不僅測試程式碼邏輯,也會驗證檔案中的範例是否正確。
Rust 模組系統
Rust 的模組系統用於組織程式碼,將其分成獨立的單元,並可選擇是否將其分割到不同的原始碼檔案中。模組結合了引入其他原始碼檔案和名稱空間的功能。
mod private_mod {
// 私有程式碼
}
pub mod public_mod {
// 公開程式碼,將被匯出
}
Rust 中的所有符號預設為私有,但可以使用 pub 關鍵字將其匯出。模組名稱通常使用蛇形命名法(snake case),而結構體、列舉等則使用駝峰命名法(camel case)。
內容解密:
mod關鍵字:用於宣告模組,可以包含私有或公開的程式碼。pub mod:宣告公開模組,其內容會被匯出。- 命名慣例:模組名稱使用蛇形命名法,而其他型別名稱則使用駝峰命名法。
使用Cargo進行專案管理
模組化組織與可見性
在Rust中,模組可以用巢狀結構來組織:
mod outer_mod {
mod inner_mod {
mod super_inner_mod {
// ...
}
}
}
當我們需要從其他crate引入符號或模組時,可以使用use陳述式:
use serde::ser::{Serialize, Serializer};
在use陳述式中,第一個名稱通常是我們想要引入程式碼的crate名稱,後面跟著模組、特定符號或萬用字元(*)來引入該模組中的所有符號。
可見性規範
在Rust中,除了公開的traits和enums(其關聯專案預設為公開)之外,所有東西預設都是私有的。私有範圍的宣告被繫結到一個模組,意味著它們可以從宣告它們的模組(及其子模組)中存取。
使用pub關鍵字可以改變可見性為公開,並可選擇性地加上修飾符。例如,pub(crate)指定一個專案在crate內是公開的,但無法在crate外存取。
只有當模組本身也是公開的時候,在模組內宣告的專案才會被匯出到crate的作用域之外。例如,在下面的程式碼中,我們有兩個公開函式,但只有public_mod_fn()在crate外是可見的:
mod private_mod {
pub fn private_mod_fn() {}
}
pub mod public_mod {
pub fn public_mod_fn() {}
}
此外,在公開模組內的私有專案仍然是私有的,無法在crate外存取。
使用檔案系統組織模組
我們也可以使用檔案系統來組織模組。考慮一個具有以下結構的crate:
.
├── Cargo.lock
├── Cargo.toml
└── src
├── lib.rs
├── outer_module
│ └── inner_module
│ ├── mod.rs
│ └── super_inner_module.rs
└── outer_module.rs
在這個例子中,我們有三個巢狀的內部模組。在頂層的lib.rs中,我們會引入outer_module,它定義在outer_module.rs中:
mod outer_module;
編譯器會尋找outer_module.rs或outer_module/mod.rs中的模組宣告。在我們的例子中,我們提供了與lib.rs同級的outer_module.rs。
在outer_module.rs中,我們有以下內容來引入inner_module:
mod inner_module;
編譯器接下來會在outer_module中尋找inner_module.rs或inner_module/mod.rs。在這個例子中,它找到了包含以下內容的inner_module/mod.rs:
mod super_inner_module;
這引入了位於inner_module目錄下的super_inner_module.rs。
內容解密:
- 模組宣告:使用
mod關鍵字來宣告模組,可以用巢狀結構或檔案系統來組織。 - 可見性控制:使用
pub關鍵字來控制模組或專案的可見性,可以指定為crate、self、super或特定的路徑。 - 檔案系統組織:利用檔案系統的目錄結構來組織模組,使得大型專案更容易管理。
工作空間(Workspaces)
Cargo的工作空間功能允許你將一個大型crate分解為多個獨立的crate,並將這些crate集中在一個分享單一Cargo.lock鎖設定檔的工作空間中。工作空間有幾個重要的特性,主要特點是允許分享來自Cargo.toml的引數和來自單一Cargo.lock的解析依賴樹。
每個工作空間內的專案分享以下內容:
- 頂層的
Cargo.lock檔案 - 包含所有工作空間專案目標的
target/輸出目錄 - 頂層
Cargo.toml中的[patch]、[replace]和[profile.*]區段
要使用工作空間,你需要像平常一樣使用Cargo建立專案,但需要在不與頂層crate目錄重疊的子目錄中建立(例如,不在 src/、 target/、 tests/、 examples/、 benches/ 等目錄中)。然後,你可以像平常一樣新增依賴,但不是指定版本或倉函式庫,而是指定路徑或將每個專案新增到 workspace.members 列表中的 Cargo.toml。
建立工作空間範例
首先,建立一個頂層應用程式,並進入新建立的目錄:
$ cargo new workspaces-example
$ cd workspaces-example
接下來,建立一個子專案,這將是一個簡單的函式庫:
$ cargo new subproject --lib
更新頂層的 Cargo.toml 以包含子專案,將其新增為依賴:
[dependencies]
subproject = { path = "./subproject" }
並將子專案新增到 [workspace.members] 中,這裡存放著工作空間成員的路徑列表。
內容解密:
- 工作空間用途:用於管理多個相關聯的crate,分享依賴和組態。
- 分享內容:工作空間內的專案分享
Cargo.lock、target/目錄和特定的Cargo.toml組態區段。 - 建立子專案:使用
cargo new命令建立子專案,並透過修改Cargo.toml將其納入工作空間。
2.10 工作空間(Workspaces)
在管理多個相關的Cargo專案時,可以使用工作空間(Workspaces)來簡化專案管理。工作空間允許你將多個Cargo專案組織在同一個目錄下,並共用相同的Cargo設定。
建立工作空間
首先,建立一個新的Cargo專案,並在該專案下建立一個子專案:
$ cargo new workspaces-example
$ cd workspaces-example
$ cargo new subproject --lib
接下來,編輯Cargo.toml檔案,將子專案加入工作空間:
[workspace]
members = ["subproject"]
現在,你可以執行cargo check來確保所有專案都能正確編譯。
在工作空間中使用子專案
目前,頂層專案尚未使用子專案中的程式碼。讓我們新增一個函式hello_world,並在頂層專案中呼叫它。首先,更新subproject/src/lib.rs:
pub fn hello_world() -> String {
String::from("Hello, world!")
}
然後,更新src/main.rs:
fn main() {
println!("{}", subproject::hello_world());
}
最後,執行cargo run:
$ cargo run
Compiling subproject v0.1.0
Compiling workspaces-example v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 0.85s
Running `target/debug/workspaces-example`
Hello, world!
內容解密:
- 工作空間的定義:在
Cargo.toml中使用[workspace]區段定義工作空間,並列出所有成員專案。 - 子專案的建立:使用
cargo new subproject --lib建立一個新的子專案。 - 函式的呼叫:在頂層專案中呼叫子專案中的函式,需要使用子專案的名稱作為名稱空間。
工作空間的優點
工作空間允許你將多個相關的Cargo專案組織在一起,並共用相同的Cargo設定。這使得管理多個專案變得更加容易。
虛擬清單(Virtual Manifests)
Cargo也支援虛擬清單,這是一種頂層crate,不包含[package]區段,只包含子專案。這對於將多個套件釋出為一個整體非常有用。
2.11 自訂建置指令碼(Custom Building Scripts)
Cargo提供了一個建置時的功能,允許你指定建置時的作業。這個指令碼包含一個單一的Rust main函式,以及其他你想要包含的程式碼。
使用自訂建置指令碼
首先,建立一個新的Cargo專案:
$ cargo new build-script-example
$ cd build-script-example
接下來,建立一個C函式庫,包含一個函式,回傳字串"Hello, world!":
// src/hello_world.c
const char *hello_world(void) {
return "Hello, world!";
}
然後,更新Cargo.toml,將cccrate加入建置相依性:
[dependencies]
libc = "0.2"
[build-dependencies]
cc = "1.0"
建立建置指令碼build.rs:
// build.rs
fn main() {
println!("cargo:rerun-if-changed=src/hello_world.c");
cc::Build::new()
.file("src/hello_world.c")
.compile("hello_world");
}
內容解密:
cargo:rerun-if-changed:指示Cargo在指定的檔案變更時重新執行建置指令碼。cc::Build::new():建立一個新的cc建置器,用於編譯C程式碼。.file("src/hello_world.c"):指定要編譯的C檔案。.compile("hello_world"):指定編譯後的函式庫名稱。
最後,更新src/main.rs,呼叫C函式庫中的函式:
use libc::c_char;
use std::ffi::CStr;
extern "C" {
fn hello_world() -> *const c_char;
}
fn call_hello_world() -> &'static str {
unsafe {
CStr::from_ptr(hello_world())
.to_str()
.expect("String conversion failure")
}
}
fn main() {
println!("{}", call_hello_world());
}
執行cargo run:
$ cargo run
Compiling cc v1.0.67
Compiling libc v0.2.91
Compiling build-script-example v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 2.26s
Running `target/debug/build-script-example`
Hello, world!
內容解密:
extern "C":宣告一個外部C函式。CStr::from_ptr:將C字串指標轉換為Rust字串。to_str():將C字串轉換為Rust字串切片。
2.12 Rust在嵌入式環境中的應用
Rust是一種系統級程式語言,非常適合用於嵌入式程式設計。Rust的靜態分析工具在嵌入式領域尤其強大,可以在編譯時保證資源狀態、pin選擇和分享狀態的安全性。
Rust在嵌入式環境中的優勢
Rust的靜態分析工具可以幫助開發者在編譯時發現錯誤,避免在執行時出現問題。這對於嵌入式系統來說尤其重要,因為嵌入式系統通常資源有限,難以進行除錯和驗證。