rust 代碼組織結構

使用包、Crate 和模塊管理不斷增長的項目 - Rust 程序設計語言 中文版

rust 組織結構中,包括以下幾個概念

Package(包),Crate(箱),Moudle(模塊)

  • Package

這是 Cargo 的概念,對應一個 Cargo.toml 文件,也就是一個 rust 工程。用於構建、測試、共享 Crate。
1 package = 0/1 lib crate + 0/N bin crate

  • Crate

有 bin / lib 之分,bin 是可執行 Crate, lib 是庫 Crate。

基礎概念 - bin Crate

通過 cargo new project-name 新建一個 cargo 項目之後,默認新建的是 bin Crate. 代碼結構如下

src
  ╰-main.rs
Cargo.toml
Cargo.lock

默認約定,main.rs 表示的是 bin Crate,裏面有 main 函數入口,其 Crate 的名稱(也是產生的可執行文件的名稱)就是項目名稱。
使用 cargo run,默認運行的就是這個 Crate.

可以改嗎?可以,但通常不會這麼做。

如圖所示,這裏沒有默認的 main.rs,在 Cargo.toml 文件中顯式配置了兩個 bin Crate. 名字分別是 cat1 和 cat2(對應的可執行文件的名字也是這個)。

文檔見:Cargo Targets - The Cargo Book

此時運行 cargo run 就會有報錯

error: `cargo run` could not determine which binary to run. Use the `--bin` option to specify a binary, or the `default-run` manifest key.
available binaries: cat1, cat2

需要這要運行 cargo run --bin cat1

根據默認的約定,如果有多個 bin Crate,除了 main.rs,其它的應該放在 bin 目錄下。
如果需要 cargo run 命令能夠找到默認的 Crate,則需要配置 default-run

這裏配置 default-run = "cat", 其中的 cat 就是 main.rs 所指的 Crate,因爲默認情況下,main.rs 的 Crate 名稱就是項目(package)的名稱。

bin 中其它 Crate 的名稱,默認就是文件名。

爲什麼一個項目(package)中,可以有多個 bin 類型的 Crate,場景是什麼?

// from GPT-4
在 Rust 中,一個 package 可以包含多個 bin(binary)類型的 crate,這種特性在一些場景中非常有用。

命令行工具套件:如果你正在開發一套命令行工具,每個工具都有自己的功能,但它們共享一些公共的庫代碼。在這種情況下,你可以爲每個工具創建一個單獨的 bin crate,然後將公共代碼放在 lib crate 中。

多目標項目:如果你的項目需要生成多個不同的二進制文件,比如一個客戶端和一個服務器,或者一個前端和一個後端,你可以爲每個目標創建一個 bin crate。

示例代碼:如果你正在創建一個庫並且想要提供一些示例代碼,你可以創建一個或多個 bin crate 來展示如何使用你的庫。

在這些情況下,所有的 bin crate 都可以在同一個 package 中管理,這樣可以共享版本號、依賴和構建設置,使得項目的管理變得更加方便。

基礎概念 - lib Crate

// from GPT-4
在 Rust 中,一個 package 中只能有一個 library crate 的原因主要是爲了簡化編譯和依賴管理的過程。在 package 中,library crate 的名字與 package 的名字相同,這使得在使用 cargo build 或 cargo publish 等命令時,cargo 可以明確知道應該編譯或發佈哪個 crate。

如果一個 package 中允許有多個 library crate,那麼在處理依賴關係時,可能會產生一些複雜的問題。比如,如果兩個 library crate 有相同的名字但版本不同,那麼在解析依賴關係時就會產生衝突。另外,如果一個 package 中有多個 library crate,那麼在編譯時,cargo 也需要知道應該編譯哪個 crate,這將使得編譯過程變得更復雜。

總的來說,限制一個 package 中只有一個 library crate 是爲了簡化編譯和依賴管理的過程,使得 Rust 的 package 管理系統更易於使用。

默認約定是,lib Crate,以 lib.rs 的文件名,直接放在 src 目錄下。
或者可以通過 Crago.toml 自定義,文檔:https://doc.rust-lang.org/cargo/reference/cargo-targets.html?highlight=[[bin]]#configuring-a-target

[lib]
name = "foo"           # The name of the target.
path = "src/lib.rs"    # The source file of the target.
test = true            # Is tested by default.
doctest = true         # Documentation examples are tested by default.
bench = true           # Is benchmarked by default.
doc = true             # Is documented by default.
plugin = false         # Used as a compiler plugin (deprecated).
proc-macro = false     # Set to `true` for a proc-macro library.
harness = true         # Use libtest harness.
edition = "2015"       # The edition of the target.
crate-type = ["lib"]   # The crate types to generate.
required-features = [] # Features required to build this target (N/A for lib).

同一個項目(package)中,bin Crate 可以直接引用 lib 中定義的內容,不需要在 Cargo.toml 中做額外的聲明。

Module

從模塊的角度理解 Crate,一個 Crate 就是一棵 Module 組成的樹。其中樹的根節點(一個隱式根節點)的名稱,就是 crate

// lib.rs
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

pub mod sub_module {
    pub fn calc(a: i32, b: i32) -> i32 {
        crate::add(a, b)
    }
}

比如在 lib.rs 中定義了 add 這個函數,就是在 crate 這個隱式的根 Module 下面,可以使用 create::add 的方式,引用到這個函數。

在上面的例子中,除了使用隱式的根名稱 crate,也可以使用 super 關鍵字

// lib.rs
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

pub mod sub_module {
    pub fn calc(a: i32, b: i32) -> i32 {
        super::add(a, b)
    }
}

super 指的是上一級。

Module 多文件

當然,通常情況下,不會在 lib.rs 中直接寫具體的實現,而是使用 lib.rs 來進行統一的導出。

如上圖中,在 math 子文件夾下,有一個 arithmetic.rs 文件,這裏,math 和 arithmetic 就直接被視爲兩個模塊,arithmetic 是 math 的子模塊。

lib.rs 中,使用如下代碼進行導出。

pub mod math {
    pub mod arithmetic;
}

在 main.rs 中進行使用

use cat::math;

fn main() {
    let sum = math::arithmetic::add(1, 2);

    println!("sum = {}", sum);
}

在 jump.rs 中進行使用

use cat::math::arithmetic;

fn main() {
    let sum = arithmetic::add(1, 2);

    println!("Hello, jump {}", sum);
}

pub

pub 就是 public, 所有聲明的模塊,函數,struct 等,默認都是私有的,都是使用 pub 將其公開。
如何僅在 Crate 內公開,而不在 Crate 外公開呢?簡單的做法:在 lib.rs 中不導出就可以了。

pub 還有更精細的控制:
Visibility and privacy - The Rust Reference

  • pub(in path) makes an item visible within the provided path. path must be an ancestor module of the item whose visibility is being declared.
  • pub(crate) makes an item visible within the current crate.
  • pub(super) makes an item visible to the parent module. This is equivalent to pub(in super).
  • pub(self) makes an item visible to the current module. This is equivalent to pub(in self) or not using pub at all.
pub mod outer_mod {
    pub mod inner_mod {
        // This function is visible within `outer_mod`
        pub(in crate::outer_mod) fn outer_mod_visible_fn() {}
        // Same as above, this is only valid in the 2015 edition.
        pub(in outer_mod) fn outer_mod_visible_fn_2015() {}

        // This function is visible to the entire crate
        pub(crate) fn crate_visible_fn() {}

        // This function is visible within `outer_mod`
        pub(super) fn super_mod_visible_fn() {
            // This function is visible since we're in the same `mod`
            inner_mod_visible_fn();
        }

        // This function is visible only within `inner_mod`,
        // which is the same as leaving it private.
        pub(self) fn inner_mod_visible_fn() {}
    }
    pub fn foo() {
        inner_mod::outer_mod_visible_fn();
        inner_mod::crate_visible_fn();
        inner_mod::super_mod_visible_fn();

        // This function is no longer visible since we're outside of `inner_mod`
        // Error! `inner_mod_visible_fn` is private
        //inner_mod::inner_mod_visible_fn();
    }
}

fn bar() {
    // This function is still visible since we're in the same crate
    outer_mod::inner_mod::crate_visible_fn();

    // This function is no longer visible since we're outside of `outer_mod`
    // Error! `super_mod_visible_fn` is private
    //outer_mod::inner_mod::super_mod_visible_fn();

    // This function is no longer visible since we're outside of `outer_mod`
    // Error! `outer_mod_visible_fn` is private
    //outer_mod::inner_mod::outer_mod_visible_fn();

    outer_mod::foo();
}

fn main() { bar() }

use

將其它模塊、struct、enum、函數等的定義,引入到當前作用域。
慣用做法:
引入函數時,只引入到函數的上層模塊,然後通過 xxx::func() 的形式調用,函數歸屬會比較清晰;
引入 struct、enum 時,就全路徑直接引入。

  • as 爲引入的路徑取別名。
use std::fmt::Result;
use std::io::Result as IoResult;

fn f1() -> Result {}
fn f2() -> IoResult {}
  • pub use

使用 pub use 可以將與引用到的內容重新導出,這個過程中,就有機會對導出做出調整

比如上例中的 lib.rs

// lib.rs
mod math {
    pub mod arithmetic;
}

pub use math::arithmetic;  // 直接導出 arithmetic 模塊
// main.rs
use cat::arithmetic; // 直接引入 arithmetic 模塊,這裏沒有 math 了

fn main() {
    let sum = arithmetic::add(1, 2);

    println!("sum = {}", sum);
}
  • 嵌套路徑
use std::cmp::Ordering;
use std::io;

use std::{cmp::Ordering, io};
use std::io;
use std::io::Write;

use std::io::{self, Write};
  • 通配符全部引入
use std::collections::*;
  • 引入第三方庫
[dependencies]
rand = "0.7.0"

Dependency Resolution - The Cargo Book

prelude

prelude 就是 Crate 中的一個模塊,只不過通常這個模塊用來導出這個 Crate 中,最常用的一些 trait 和數據結構定義等。

如 rand 的 prelude
rand::prelude - Rust

使用 use rand::prelude::*; 導入全部的預定義內容,然後就可以直接使用了。

use rand::prelude::*;

fn main() {
    let r: bool = random(); // OK,直接使用
    println!("r = {}", r);

    let r: i32 = rand::random();  // 常規用法:通過 rand Crate name 調用
    println!("r = {}", r);
}

高級特性 Workspace

Cargo 的 Workspace 機制,通常一個項目就是一個 package,但當項目足夠複雜時,一個項目中可以有多個 package,組成一個 Workspace。

案例:
wasmerio/wasmer: 🚀 The leading WebAssembly Runtime supporting WASIX, WASI and Emscripten

參考

Rust 的包管理機制
Rust 程序設計語言 - Rust 程序設計語言 中文版
Rust 組織管理 | 菜鳥教程
Introduction - The Cargo Book
Introduction - The Rust Reference

https://www.cnblogs.com/jasongrass/p/17728205.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章