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)] 屬性會讓編譯器對缺少檔案的公開函式或型別發出警告。

內容解密:

  1. //! 註解:用於 crate 或模組層級的檔案,通常放在檔案開頭,描述整個 crate 或模組的功能。
  2. #![warn(missing_docs)]:編譯器屬性,用於檢查公開 API 是否有檔案,若無則發出警告。
  3. /// 註解:用於函式或型別的檔案,描述其功能與用法。

檔案中的程式碼範例

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

內容解密:

  1. 檔案中的程式碼範例:使用 Markdown 語法標記範例程式碼,並確保其正確性和可執行性。
  2. cargo test 的作用:不僅測試程式碼邏輯,也會驗證檔案中的範例是否正確。

Rust 模組系統

Rust 的模組系統用於組織程式碼,將其分成獨立的單元,並可選擇是否將其分割到不同的原始碼檔案中。模組結合了引入其他原始碼檔案和名稱空間的功能。

mod private_mod {
    // 私有程式碼
}

pub mod public_mod {
    // 公開程式碼,將被匯出
}

Rust 中的所有符號預設為私有,但可以使用 pub 關鍵字將其匯出。模組名稱通常使用蛇形命名法(snake case),而結構體、列舉等則使用駝峰命名法(camel case)。

內容解密:

  1. mod 關鍵字:用於宣告模組,可以包含私有或公開的程式碼。
  2. pub mod:宣告公開模組,其內容會被匯出。
  3. 命名慣例:模組名稱使用蛇形命名法,而其他型別名稱則使用駝峰命名法。

使用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.rsouter_module/mod.rs中的模組宣告。在我們的例子中,我們提供了與lib.rs同級的outer_module.rs

outer_module.rs中,我們有以下內容來引入inner_module

mod inner_module;

編譯器接下來會在outer_module中尋找inner_module.rsinner_module/mod.rs。在這個例子中,它找到了包含以下內容的inner_module/mod.rs

mod super_inner_module;

這引入了位於inner_module目錄下的super_inner_module.rs

內容解密:

  1. 模組宣告:使用 mod 關鍵字來宣告模組,可以用巢狀結構或檔案系統來組織。
  2. 可見性控制:使用 pub 關鍵字來控制模組或專案的可見性,可以指定為 crateselfsuper 或特定的路徑。
  3. 檔案系統組織:利用檔案系統的目錄結構來組織模組,使得大型專案更容易管理。

工作空間(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] 中,這裡存放著工作空間成員的路徑列表。

內容解密:

  1. 工作空間用途:用於管理多個相關聯的crate,分享依賴和組態。
  2. 分享內容:工作空間內的專案分享 Cargo.locktarget/ 目錄和特定的 Cargo.toml 組態區段。
  3. 建立子專案:使用 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!

內容解密:

  1. 工作空間的定義:在Cargo.toml中使用[workspace]區段定義工作空間,並列出所有成員專案。
  2. 子專案的建立:使用cargo new subproject --lib建立一個新的子專案。
  3. 函式的呼叫:在頂層專案中呼叫子專案中的函式,需要使用子專案的名稱作為名稱空間。

工作空間的優點

工作空間允許你將多個相關的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");
}

內容解密:

  1. cargo:rerun-if-changed:指示Cargo在指定的檔案變更時重新執行建置指令碼。
  2. cc::Build::new():建立一個新的cc建置器,用於編譯C程式碼。
  3. .file("src/hello_world.c"):指定要編譯的C檔案。
  4. .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!

內容解密:

  1. extern "C":宣告一個外部C函式。
  2. CStr::from_ptr:將C字串指標轉換為Rust字串。
  3. to_str():將C字串轉換為Rust字串切片。

2.12 Rust在嵌入式環境中的應用

Rust是一種系統級程式語言,非常適合用於嵌入式程式設計。Rust的靜態分析工具在嵌入式領域尤其強大,可以在編譯時保證資源狀態、pin選擇和分享狀態的安全性。

Rust在嵌入式環境中的優勢

Rust的靜態分析工具可以幫助開發者在編譯時發現錯誤,避免在執行時出現問題。這對於嵌入式系統來說尤其重要,因為嵌入式系統通常資源有限,難以進行除錯和驗證。