精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Gopher的Rust第一課:Rust代碼組織

開發 前端
在實際開發中,使用Cargo來創建和管理Rust包是常見的做法。在本章的后半段,我們介紹了使用cargo管理的rust項目的代碼組織情況,包括單package項目和多package項目以及如何為項目引入外部和內部依賴。

在上一章的講解中,我們編寫了第一個Rust示例程序"hello, world",并給出了rustc版和cargo版本。在真實開發中,我們都會使用cargo來創建和管理Rust包。不過,Hello, world示例非常簡單,僅僅由一個Rust源碼文件組成,而且所有源碼文件都在同一個目錄中。但真實世界中的實用Rust程序,無論是公司商業項目,還是一些知名的開源項目,甚至是一些稍復雜一些的供教學使用的示例程序,它們通常可不會這么簡單,都有著復雜的代碼結構。

Rust初學者在閱讀這些項目源碼時便仿佛進入了迷宮,不知道該走哪條(閱讀代碼的)路徑,不知道每個目錄代表的含義,也不知道自己想看的源碼究竟在哪個目錄下。但目前市面上的Rust入門教程大多沒有重視初學者的這一問題,要么沒有對Rust項目代碼組織結構進行針對性的講解,要么是將講解放到書籍的后面章節。

根據我個人的學習經驗來看,理解一個實用Rust項目的代碼組織結構越早,對后續的Rust學習越有益處。同時,掌握Rust項目的代碼組織結構也是Rust開發者走向編寫復雜Rust程序的必經的一步。并且,初學者在了解項目的代碼組織結構后,便可以自主閱讀一些復雜的Rust項目的源碼,可提高Rust學習的效率,提升學習效果。因此,我決定在介紹Rust基礎語法之前先在本章中系統地介紹Rust的代碼組織結構,以滿足很多Rust初學者的述求。

但在介紹Rust代碼組織結構之前,我們需要先來系統說明一下Rust代碼組織結構中的幾個重要概念,它們是了解Rust項目代碼組織結構的前提。

4.1 回顧Go代碼組織

Go項目代碼組織由module和package兩級組成。通常來說,每個Go repo就是一個module,由repo根目錄下的go.mod定義,go.mod文件所在目錄也被稱為module root。go.mod中典型內容如下:

// go.mod
module github.com/user/mymodule[/vN]

go 1.22.1

... ...

go.mod中的module directive一行后面的github.com/user/mymodule/[vN]是module path。module path一來可以反映該module的具體網絡位置,同時也是該module下面的Go package導入(import)路徑的組成部分。module root下的子目錄中通常存放著該module下面的Go package,比如module root/foo目錄下存放的Go包的導入路徑為github.com/user/mymodule[/vN]/foo。

Go package是Go的編譯單元,也是功能單元,代碼內外部導入和引用的單位也都是包。而go module是后加入的,更多用于管理包的版本(一個module下的所有包都統一進行版本管理)以及構建時第三方依賴和版本的管理。

更多關于Go module和package管理以及Go項目布局的內容,可以詳見我的極客時間《Go語言第一課》[1]專欄。

個人認為Go的module和package的兩級管理還是很好理解和管理的,在這方面Rust的代碼組織形式又是怎樣的呢?接下來,我們就來正式看看Rust的代碼組織。

4.2 rustc-only的Rust項目

Rust是系統編程語言,這讓我想起了當初在Go成為我個人主力語言之前使用C/C++進行開發的歲月。C/C++是沒有像go或Rust的cargo那樣的統一的包依賴管理器和項目構建管理工具的。編譯器(如gcc等)是核心工具,而項目構建管理則經常由其他工具負責,如Makefile、CMake,或者是Google的Bazel[2]等。在Windows上開發應用的,則往往使用微軟或其他開發者工具公司提供的IDE,如當年炙手可熱的Visual Studio系列。

下面表格展示了各語言的編譯器/鏈接器和構建管理工具的關系:

圖片圖片

像cargo、go這樣的“一站式”工具鏈都旨在為開發者提供體驗更為友好的交互接口的,在幕后,它們仍然依賴于底層的編譯器和鏈接器(如rustc和go tool compile/link)來執行實際的代碼編譯。

不過,像cargo這樣的高級工具也給開發人員帶來了額外的抽象,或是叫“掩蓋”了一些真相,這有時候讓人看不清構建過程的本質,比如:很多Gopher用了很多年Go,但卻不知道go tool compile/link的存在。

本著只有in hard way,才能看到和抓住本質的思路,以及之前學習用系統編程語言C/C++時經驗,這里我們先來看一些rustc-only的Rust項目。Rustc-only的Rust項目是指不使用Cargo創建和管理的Rust項目,而是直接使用rustc編譯器來編譯和構建項目。這意味著開發者需要編寫自己的構建腳本,例如使用Makefile或其他構建工具來管理項目的構建過程。

不過,請注意:這類項目極少用于生產,即便是那些不需要復雜的依賴管理的小型項目。這里使用rustc-only的Rust項目僅僅是為了學習和了解Rustc編譯器的主要功能機制以及Rust語言在代碼組織上的一些抽象,比如module等。

下面我們就從最簡單的rustc-only項目開始,先來看看只有一個Rust源文件且無其他依賴項的“最簡項目”。

4.2.1 單文件項目

所謂單文件項目,即只有一個Rust源文件,例如前面章節中的hello_world.rs,這種項目可以直接使用rustc編譯器來編譯和運行:

// rust-guide-for-gopher/organizing-rust-code/rustc-only/single/hello-world/hello_world.rs
fn main() {
    println!("Hello, world!");
}

對于頂層帶有main函數的源文件,rustc會默認將其視為binary crate類型的源文件,并將其編譯為可執行二進制文件hello_world。

我們當然也可以強制的讓rustc將該源文件視為library crate類型的源文件,并將其編譯為其他類型的crate輸出文件,rustc支持多種crate type:

--crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]
                        Comma separated list of types of crates
                        for the compiler to emit

rustc的文檔[3]中,各種crate類型的含義如下:

lib — Generates a library kind preferred by the compiler, currently defaults to rlib.
rlib — A Rust static library.
staticlib — A native static library.
dylib — A Rust dynamic library.
cdylib — A native dynamic library.
bin — A runnable executable program.
proc-macro — Generates a format suitable for a procedural macro library that may be loaded by the compiler.

不過,如果強制將帶有頂層main函數的rust源文件視為lib crate型的,那么rustc將會報warning,提醒你函數main將是死代碼,永遠不會被用到:

$rustc --crate-type lib hello_world.rs
warning: function `main` is never used
 --> hello_world.rs:1:4
  |
1 | fn main() {
  |    ^^^^
  |
  = note: `#[warn(dead_code)]` on by default

warning: 1 warning emitted

但即便如此,一個名為libhello_world.rlib的文件依然會被rustc生成出來!(目前--crate-type lib等同于--create-type rlib)。

4.2.2 有外部依賴項的單文件項目

日常開發中,像上面的Hello, World級別的trivial應用是極其少見的,一個non-trivial的Rust應用或多或少都會有一些依賴。這里我們也來看一下如何基于rustc來構建帶有外部依賴的單文件項目。下面是一個帶有外部依賴的示例:

// organizing-rust-code/rustc-only/single/hello-world-with-deps/hello_world.rs
extern crate rand;
  
use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();
    let num: u32 = rng.gen();
    println!("Random number: {}", num);
}

這個示例程序依賴一個名為rand的crate,要編譯該程序,我們必須先手動下載rand的crate源碼,并在本地將rand源碼編譯為示例程序所需的rust library。下面步驟展示了如何下載和構建rand crate:

$curl -LO https://crates.io/api/v1/crates/rand/0.8.5/download
$tar -xvf download

解壓后,我們將看到rand-0.8.5這樣的一個crate目錄,進入該目錄,我們執行cargo build來構建rand crate:

$cd rand-0.8.5
$cargo build
... ...
   Finished dev [unoptimized + debuginfo] target(s) in 0.19s

cargo構建出的librand.rlib就在rand-0.8.5/target/debug下。

注:rlib的命名方式:lib+{crate_name}.rlib

接下來,我們就來構建一下依賴rand crate的hello_world.rs:

// 在organizing-rust-code/rustc-only/single/hello-world-with-deps下面執行

$rustc --verbose  -L ./rand-0.8.5/target/debug  --extern rand=librand.rlib hello_world.rs
error[E0463]: can't find crate for `rand_core` which `rand` depends on
 --> hello_world.rs:1:1
  |
1 | extern crate rand;
  | ^^^^^^^^^^^^^^^^^^ can't find crate

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0463`.

我們看到rustc的編譯錯誤提示:無法找到rand crate依賴的rand_core crate!也就是說我們除了向rustc提供hello_world.rs依賴的rand crate之外,還要向rustc提供rand crate的各種依賴!

rand crate的各種依賴在哪里呢?我們在構建rand crate時,cargo build將各種依賴都放在了rand-0.8.5/target/debug/deps目錄下了:

$ls -l|grep ".rlib"
-rw-r--r--   1 tonybai  staff     6896  4 29 06:45 libcfg_if-cd6bebf18fb9c234.rlib
-rw-r--r--   1 tonybai  staff   204072  4 29 06:45 libgetrandom-df6a8e95e188fc56.rlib
-rw-r--r--   1 tonybai  staff  1651320  4 29 06:45 liblibc-f16531562d07b476.rlib
-rw-r--r--   1 tonybai  staff   959408  4 29 06:45 libppv_lite86-f1d97d485bc43617.rlib
-rw-r--r--   1 tonybai  staff  1784376  4 29 06:45 librand-9a91ea8db926e840.rlib
-rw-r--r--   1 tonybai  staff   987936  4 29 06:45 librand_chacha-6fe22bd8b3bb228c.rlib
-rw-r--r--   1 tonybai  staff   256768  4 29 06:45 librand_core-fc905f6ca5f8533b.rlib

我們看到其中還包含了librand自身:librand-9a91ea8db926e840.rlib。我們來試試基于deps目錄下的這些依賴rlib編譯一下:

$rustc --verbose  --extern rand=rand-0.8.5/target/debug/deps/librand-9a91ea8db926e840.rlib -L rand-0.8.5/target/debug/deps  --extern rand_core=librand_core-fc905f6ca5f8533b.rlib --extern getrandom=libgetrandom-df6a8e95e188fc56.rlib --extern cfg_if=libcfg_if-cd6bebf18fb9c234.rlib --extern libc=liblibc-f16531562d07b476.rlib --extern rand_chacha=librand_chacha-6fe22bd8b3bb228c.rlib --extern ppv_lite86=libppv_lite86-f1d97d485bc43617.rlib  hello_world.rs

我們用rustc成功編譯了帶有外部依賴的Rust源碼。不過這里要注意的是rustc對直接依賴和間接依賴的crate的定位方式有所不同。

對于直接依賴的crate,比如這里的rand crate,我們需要給出具體路徑,它不依賴-L的位置指示,所以這里我們使用了--extern rand=rand-0.8.5/target/debug/deps/librand-9a91ea8db926e840.rlib。

對于間接依賴的crate,比如rand crate依賴的rand_core,rust會結合-L指示的位置以及--extern一起來定位,這里-L指示路徑為rand-0.8.5/target/debug/deps,--extern rand_core=librand_core-fc905f6ca5f8533b.rlib,那么rustc就會在rand-0.8.5/target/debug/deps下面搜索librand_core-fc905f6ca5f8533b.rlib是否存在。

我們運行rustc構建出的可執行文件,輸出如下:

$./hello_world 
Random number: 431751199

4.2.3 有外部依賴的多文件項目

在Go中,如果某個目錄下有多個源文件,那么通常這幾個源文件均歸屬于同一個Go包(可能的例外的是*_test.go文件的包名)。但在Rust中,情況就會變得復雜了一些,我們來看一個例子:

// organizing-rust-code/rustc-only/multi/multi-file-with-deps

$tree -F -L 2
.
├── main.rs
├── sub1/
│   ├── bar.rs
│   ├── foo.rs
│   └── mod.rs
└── sub2.rs

在這個示例中,我們看到除了main.rs之外,還有一個sub2.rs以及一個目錄sub1,sub1下面還有三個rs文件。我們從main.rs開始,逐一看一下各個源文件的內容:

// organizing-rust-code/rustc-only/multi/multi-file-with-deps/main.rs
 1 extern crate rand;
 2 use rand::Rng;
 3 
 4 mod sub1;
 5 mod sub2;
 6 
 7 mod sub3 {
 8     pub fn func1() {
 9         println!("called {}::func1()", module_path!());
10     }
11     pub fn func2() {
12         self::func1();
13         println!("called {}::func2()", module_path!());
14         super::func1();
15     }
16 }
17 
18 fn func1() {
19     println!("called {}::func1()", module_path!());
20 }
21 
22 fn main() {
23     println!("current module: {}", module_path!());
24     let mut rng = rand::thread_rng();
25     let num: u32 = rng.gen();
26     println!("Random number: {}", num);
27 
28     sub1::func1();
29     sub2::func1();
30     sub3::func2();
31 }

在main.rs中,我們除了看到了第1~2行的對外部rand crate的依賴外,我們還看到了一種新的語法元素:rust module。這里涉及sub1~sub3三個module,我們分別來看一下。先來看一下最直觀的、定義在main.rs中的sub3 module。

第7行~第16行的代碼定義了一個名為sub3的module,它包含兩個函數func1和func2,這兩個函數前面的pub關鍵字表明他們是sub3 module的publish函數,可以被module之外的代碼所訪問。任何未標記為pub的函數都是私有的,只能在模塊內部及其子模塊中使用。

在sub3 module的func2函數中,我們調用了self::func1()函數,self指代是模塊自身,因此這個self::func1()函數就是sub3的func1函數。而接下來調用的super::func1()調用的語義你大概也能猜到。super指代的是sub3的父模塊,而super::func1()就是sub3的父模塊中的func1函數。

sub3的父模塊就是這個項目的頂層模塊,我們在main函數的入口處使用module_path!宏輸出了該頂層模塊的名稱。

和sub3在main.rs中定義不同,sub1和sub2也分別代表了另外兩種module的定義方式。

當Rust編譯器看到第4行mod sub1后,它會尋找當前目錄下是否有名為sub1.rs的源文件或是sub1/mod.rs源文件。在這個示例中,sub1定義在sub1目錄下的mod.rs中:

// organizing-rust-code/rustc-only/multi/multi-file-with-deps/sub1/mod.rs

pub mod bar;
pub mod foo;

pub fn func1() {
    println!("called {}::func1()", module_path!());
    foo::func1();
    bar::func1();
}

我們看到sub1/mod.rs中定義了一個公共函數func1,同時也在最開始處又嵌套定義了bar和foo兩個module,并在func1中調用了兩個嵌套子module的函數:

bar和foo兩個module都是使用單文件module定義的,編譯器會在sub1目錄下搜尋foo.rs和bar.rs:

// organizing-rust-code/rustc-only/multi/multi-file-with-deps/sub1/foo.rs
pub fn func1() {
    println!("called {}::func1()", module_path!());
}

// organizing-rust-code/rustc-only/multi/multi-file-with-deps/sub1/bar.rs
pub fn func1() {
    println!("called {}::func1()", module_path!());
}

而main.rs中的sub2也是一個單文件的module,其源碼位于頂層目錄下的sub2.rs文件中:

// organizing-rust-code/rustc-only/multi/multi-file-with-deps/sub2.rs
pub fn func1() {
    println!("called {}::func1()", module_path!());
}

現在我們來編譯和執行一下這個既有外部依賴,又是多文件且有多個module的rustc-only項目:

$rustc --verbose  --extern rand=rand-0.8.5/target/debug/deps/librand-9a91ea8db926e840.rlib -L rand-0.8.5/target/debug/deps  --extern rand_core=librand_core-fc905f6ca5f8533b.rlib --extern getrandom=libgetrandom-df6a8e95e188fc56.rlib --extern cfg_if=libcfg_if-cd6bebf18fb9c234.rlib --extern libc=liblibc-f16531562d07b476.rlib --extern rand_chacha=librand_chacha-6fe22bd8b3bb228c.rlib --extern ppv_lite86=libppv_lite86-f1d97d485bc43617.rlib  main.rs 

$./main
current module: main
Random number: 2691905579
called main::sub1::func1()
called main::sub1::foo::func1()
called main::sub1::bar::func1()
called main::sub2::func1()
called main::sub3::func1()
called main::sub3::func2()
called main::func1()

上面示例演示了三種rust module的定義方法:

  1. 直接將定義嵌入在某個rust源文件中:
mod module_name {

}
  1. 通過module_name.rs
  2. 通過module_name/mod.rs

在一個單crate的項目中,通過rust module可以滿足項目內部代碼組織的需要。

最后,我們再來看一個有多個crate的項目形式。

4.2.4 有多個crate的項目

下面是一個有著多個crate項目的示例:

// organizing-rust-code/rustc-only/workspace

$tree -L 2 -F
.
├── main.rs
├── my_local_crate1/
│   └── lib.rs
└── my_local_crate2/
    └── lib.rs

在這個示例中有三個crate,一個是頂層的binary類型的crate,入口為main.rs,另外兩個都是lib類型的crate,入口都在lib.rs中,我們貼一下他們的源碼:

// organizing-rust-code/rustc-only/workspace/main.rs
extern crate my_local_crate1;
extern crate my_local_crate2;

fn main() {
    let x = 5;
    let y = my_local_crate1::add_one(x);
    let z = my_local_crate2::multiply_two(y);
    println!("Result: {}", z);
}

// organizing-rust-code/rustc-only/workspace/my_local_crate1/lib.rs 
pub fn add_one(x: i32) -> i32 {
    x + 1
}

// organizing-rust-code/rustc-only/workspace/my_local_crate2/lib.rs 
pub fn multiply_two(x: i32) -> i32 {
    x * 2
}

要構建這個帶有三個crate的項目,我們需要首先編譯my_local_crate1和my_local_crate2這兩個lib crates:

$rustc --crate-type lib --crate-name my_local_crate1 my_local_crate1/lib.rs
$rustc --crate-type lib --crate-name my_local_crate2 my_local_crate2/lib.rs

這會在項目頂層目錄下生成兩個rlib文件:

$ls  |grep rlib 
libmy_local_crate1.rlib
libmy_local_crate2.rlib

之后,我們就可以用之前學到的方法編譯binary crate了:

$rustc --extern my_local_crate1=libmy_local_crate1.rlib --extern my_local_crate2=libmy_local_crate2.rlib main.rs

上述的幾個rustc-only的rust項目都是hard模式的,即一切都需要手工去做,包括下載crate、編譯crate時傳入各種路徑等。在真正的生產中,Rustacean們是不會這么做的,而是會直接使用cargo對rust項目進行管理。接下來,我們就來系統地看一下使用cargo進行rust項目管理以及對應的rust代碼組織形式。

4.3 使用cargo管理的Rust項目

在前面的章節中,我們見識過了:Rust的包管理器Cargo是一個強大的工具,可以幫助我們輕松地管理Rust項目,cargo才是生產類項目的項目構建管理工具標準,它可以讓Rustacean避免復雜的手工rustc操作。Cargo提供了許多功能,包括依賴項管理、構建和測試等。不過在這篇文章中,我不會介紹這些功能,而是看看使用cargo管理的Rust項目都有哪些代碼組織模式。

Rust項目的代碼組織結構可以分為兩類:單一package和多個package。

什么是package?在之前的rust-only項目中,我們可從未見到過package!package是cargo引入的一個管理單元概念,它指的是一個獨立的Rust項目,包含了源代碼、依賴項和配置信息。每個Package都有一個唯一的名稱和版本號,用于標識和管理項目。因此,在the cargo book[4]中,cargo也被稱為“Rust package manager”,crates.io也被稱為“the Rust community’s package registry”。

最能直觀體現package存在的就是下面Cargo.toml中的配置了:

[package]
name = "hello_world"
version = "0.1.0"
edition = "2021"

[dependencies]

下面我們就來看看不同類型的rust package的代碼組織形式。我們先從單一package形態的項目來開始。

4.3.1 單一package的rust項目

單一package項目是指整個項目只有一個Cargo.toml文件。這種項目還可以進一步分為三類:

  1. 單一Binary Crate
  2. 單一Library Crate
  3. 多個Binary Crate和一個Library Crate

下面我們分別舉例來說明一下這三類項目。

4.3.1.1 單一Binary Crate

我們進入organizing-rust-code/cargo/single-package/single-binary-crate,然后執行下面命令來創建一個單一Binary Crate的項目:

$cargo new hello_world --bin
     Created binary (application) `hello_world` package

這個例子我們在之前的章節中也是見過的,它的結構如下:

$tree hello_world 
hello_world
├── Cargo.toml
└── src
    └── main.rs

1 directory, 2 files

默認生成的Cargo.toml內容如下:

[package]
name = "hello_world"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

使用cargo build即可完成該項目的構建:

$cargo build
   Compiling hello_world v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/single-package/single-binary-crate/hello_world)
    Finished dev [unoptimized + debuginfo] target(s) in 1.16s

為了更顯式地體現這是一個binary crate,我們可以在Cargo.toml增加如下內容:

[[bin]]
name = "hello_world"
path = "src/main.rs"

這不會影響cargo的構建結果!

通過cargo run可以查看構建出的可執行文件的運行結果:

$cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.06s
     Running `target/debug/hello_world`
Hello, world!

接下來,我們再來看看單一library crate的rust項目。

4.3.1.2 單一Library Crate

我們進入organizing-rust-code/cargo/single-package/single-library-crate,然后執行下面命令來創建一個單一Library Crate的項目:

$cargo new my_library --lib
     Created library `my_library` package

創建后的my_library項目的結構如下:

$tree
.
├── Cargo.toml
└── src
    └── lib.rs

默認生成的Cargo.toml如下:

[package]
name = "my_library"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

和binary crate的一樣,我們也可以顯式指定target:

[lib]
name = "my_library"
path = "src/lib.rs"

注意,這里是[lib]而不是[[lib]],這是因為在一個carge package中最多只能存在一個library crate,但binary crate可以有多個。

接下來,我們就看看一個由多個binary crate和一個library crate混合構成的rust項目。

4.3.1.3 多個Binary Crate和一個Library Crate

我們在organizing-rust-code/cargo/single-package/hybrid-crates下面執行如下命令創建這個多crates混合項目:

$cargo new my_project
     Created binary (application) `my_project` package

上述命令默認創建了一個binary crate的project,我們需要配置一下Cargo.toml,將其改造為多個crates并存的project:

[package]
name = "my_project"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "cmd1"
path = "src/main1.rs"

[[bin]]
name = "cmd2"
path = "src/main2.rs"

[lib]
name = "my_library"
path = "src/lib.rs"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

這里定義了三個crates。兩個binary crates: cmd1、cmd2以及一個library crate:my_library。

如果我們執行cargo build,cargo會將三個crate都構建出來:

$cargo build
   Compiling my_project v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/single-package/hybrid-crates/my_project)
    Finished dev [unoptimized + debuginfo] target(s) in 0.80s

我們可以在target/debug下找到構建出的crates:cmd1、cmd2和libmy_library.rlib:

$ls target/debug
build/   cmd1.d   cmd2.d   examples/  libmy_library.d
cmd1*   cmd2*   deps/   incremental/  libmy_library.rlib

我們也可以通過cargo分別運行兩個binary crate:

$cargo run --bin cmd1
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/cmd1`
cmd1

$cargo run --bin cmd2
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/cmd2`
cmd2

4.3.1.4 典型的cargo package

在The cargo book中,有一個典型的cargo package的示例:

.
├── Cargo.lock
├── Cargo.toml
├── src/
│   ├── lib.rs
│   ├── main.rs
│   └── bin/
│       ├── named-executable.rs
│       ├── another-executable.rs
│       └── multi-file-executable/
│           ├── main.rs
│           └── some_module.rs
├── benches/
│   ├── large-input.rs
│   └── multi-file-bench/
│       ├── main.rs
│       └── bench_module.rs
├── examples/
│   ├── simple.rs
│   └── multi-file-example/
│       ├── main.rs
│       └── ex_module.rs
└── tests/
    ├── some-integration-tests.rs
    └── multi-file-test/
        ├── main.rs
        └── test_module.rs

在這樣一個典型的項目中:

  • Cargo.toml和Cargo.lock文件存儲在包的根目錄(包根目錄)中。
  • 源代碼位于src目錄中。
  • 默認的庫文件是src/lib.rs。
  • 默認的可執行文件是src/main.rs。
  • 其他可執行文件可以放在src/bin/目錄中。
  • 基準測試位于benches目錄中。
  • 示例位于examples目錄中。
  • 集成測試位于tests目錄中。

4.3.2 多package的rust項目

一些中大型的Rust項目都是多package的,比如rust的異步編程事實標準tokio庫[5]、剛剛升級為Apache基金會頂級項目的SQL查詢引擎datafusion[6]等。以tokio為例,這些項目的頂層Cargo.toml都是這樣的:

// https://github.com/tokio-rs/tokio/blob/master/Cargo.toml
[workspace]
resolver = "2"
members = [
  "tokio",
  "tokio-macros",
  "tokio-test",
  "tokio-stream",
  "tokio-util",

  # Internal
  "benches",
  "examples",
  "stress-test",
  "tests-build",
  "tests-integration",
]

[workspace.metadata.spellcheck]
config = "spellcheck.toml"

上面這個Cargo.toml示例與我們在前面見到的Cargo.toml都不一樣,它并不包含package配置,其主要的配置為workspace。我們看到workspace的members字段中配置了該項目下的其他package。正是通過這個配置,cargo可以在一個項目里管理和構建多個package。

工作空間(Workspace)[7]是一組一個或多個包(Package)的集合,這些包稱為工作空間成員(Workspace Members),它們一起被管理。接下來,我們就來創建一個多package的cargo項目。

4.3.2.1 cargo管理的多package項目

由于cargo并沒有提供cargo new my-pakcage --workspace這樣的命令行參數,項目的頂層Cargo.toml需要我們手動創建和編輯。

$cd organizing-rust-code/cargo/multi-packages
$mkdir my-workspace
$cd my-workspace
$cargo new package1 --bin      
     Created binary (application) `package1` package
$cargo new package2 --lib
     Created library `package2` package
$cargo new package3 --lib
     Created library `package3` package

接下來,我們手工創建和編輯一下項目頂層的Cargo.toml如下:

// organizing-rust-code/cargo/multi-packages/my-workspace/Cargo.toml
[workspace]
resolver = "2"
members = [
    "package1",
    "package2",
    "package3",
]

保存后,我們可以在項目頂層目錄下使用下面命令檢查整個工作空間(workspace)中的所有包(package),確保它們的代碼正確無誤,不包含任何編譯錯誤:

$cargo check --workspace
    Checking package1 v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/multi-packages/my-workspace/package1)
    Checking package2 v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/multi-packages/my-workspace/package2)
    Checking package3 v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/multi-packages/my-workspace/package3)
    Finished dev [unoptimized + debuginfo] target(s) in 0.18s

在頂層目錄執行cargo build,cargo會build工作空間中的所有package:

$cargo build
   Compiling package3 v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/multi-packages/my-workspace/package3)
   Compiling package2 v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/multi-packages/my-workspace/package2)
   Compiling package1 v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/multi-packages/my-workspace/package1)
    Finished dev [unoptimized + debuginfo] target(s) in 0.64s

構建后,該項目的目錄結構變成下面這個樣子:

$tree -L 2 -F
.
├── Cargo.lock
├── Cargo.toml
├── package1/
│   ├── Cargo.toml
│   └── src/
├── package2/
│   ├── Cargo.toml
│   └── src/
├── package3/
│   ├── Cargo.toml
│   └── src/
└── target/
    ├── CACHEDIR.TAG
    └── debug/

我們看到該項目下的所有package共享一個共同的 Cargo.lock 文件,該文件位于工作空間的根目錄下。并且,所有包共享一個共同的輸出目錄,默認情況下是工作空間根目錄下的一個名為target的目錄,該target目錄下的布局如下:

$tree -F -L 2 ./target
./target
├── CACHEDIR.TAG
└── debug/
    ├── build/
    ├── deps/
    ├── examples/
    ├── incremental/
    ├── libpackage2.d
    ├── libpackage2.rlib
    ├── libpackage3.d
    ├── libpackage3.rlib
    ├── package1*
    └── package1.d

我們在這下面可以找到所有package的編譯輸出結果,比如package1、libpackage2.rlib以及libpackage3.rlib。

當然,你也可以指定一個package來構建或運行:

$cargo build -p package1
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
$cargo build -p package2
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
$cargo run -p package1
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/package1`
Hello, world!

4.3.2.2 帶有外部依賴和內部依賴的多package項目

我們復制一份my-workspace,改名為my-workspace-with-deps,修改一下package1/src/main.rs,為其增加外部依賴rand crate:

// organizing-rust-code/cargo/multi-packages/my-workspace-with-deps/package1/src/main.rs
extern crate rand;

use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();
    let num: u32 = rng.gen();
    println!("Random number: {}", num);
}

接下來,我們需要修改一下package1/Cargo.toml,手工加上對rand crate的依賴配置:

// organizing-rust-code/cargo/multi-packages/my-workspace-with-deps/package1/Cargo.toml
[package]
name = "package1"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.8.5"

保存后,我們執行package1的構建:

$cargo build -p package1
  Downloaded getrandom v0.2.14 (registry `rsproxy`)
  Downloaded libc v0.2.154 (registry `rsproxy`)
  Downloaded 2 crates (780.6 KB) in 1m 07s
   Compiling libc v0.2.154
   Compiling cfg-if v1.0.0
   Compiling ppv-lite86 v0.2.17
   Compiling getrandom v0.2.14
   Compiling rand_core v0.6.4
   Compiling rand_chacha v0.3.1
   Compiling rand v0.8.5
   Compiling package1 v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/multi-packages/my-workspace-with-deps/package1)
    Finished dev [unoptimized + debuginfo] target(s) in 1m 46s

我們看到:cargo會自動下載package1的直接外部依賴以及相關間接依賴。構建成功后,可以執行一下package1的編譯結果:

$cargo run -p package1
    Finished dev [unoptimized + debuginfo] target(s) in 0.09s
     Running `target/debug/package1`
Random number: 3840180495

接下來,我們再為package1添加內部依賴,比如依賴package2的編譯結果:

// organizing-rust-code/cargo/multi-packages/my-workspace-with-deps/package1/src/main.rs

extern crate package2;
extern crate rand;

use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();
    let num: u32 = rng.gen();
    println!("Random number: {}", num);
    let result = package2::add(2, 2);
    println!("result: {}", result);
}

// organizing-rust-code/cargo/multi-packages/my-workspace-with-deps/package1/Cargo.toml
[package]
name = "package1"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.8.5"
package2 = { path = "../package2" }

我們看到:package1的main.rs依賴package2這個crate中的add函數,我們在package1的Cargo.toml中為package1添加了新依賴package2,由于package2僅僅存放在本地,所以這里我們使用了path方式指定package2的位置。

我們執行一下添加內部依賴后的package1:

$cargo run -p package1
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/package1`
Random number: 2485645524
result: 4

4.4 小結

本文循序漸進地討論了在Rust項目中如何組織代碼的問題,這對于Rust初學者來說尤為有用。

我們首先回顧了Go語言中的代碼組織方式,介紹了Go項目代碼組織的兩個層級:module和package。然后,我們將Rust項目可以分為兩種類型:使用rustc編譯器的項目和使用Cargo的項目。

對于rustc-only的項目,開發者需要編寫自己的構建腳本來管理項目的構建過程。

文章從最簡單的單文件rustc-only項目開始介紹,展示了如何使用rustc編譯器來編譯和運行這種項目,并逐步介紹了帶有外部依賴的rustc-only項目以及多文件項目的情況,引出了rust module概念。

rustc-only項目很少用于生產環境,這種方式主要用于學習和了解Rustc編譯器的功能機制以及Rust語言的代碼組織抽象。

在實際開發中,使用Cargo來創建和管理Rust包是常見的做法。在本章的后半段,我們介紹了使用cargo管理的rust項目的代碼組織情況,包括單package項目和多package項目以及如何為項目引入外部和內部依賴。

總體而言,本文旨在幫助初學者理解和掌握Rust項目的代碼組織結構,以提高學習效率和學習效果。通過介紹rustc-only項目和cargo管理的項目,讀者可以逐步了解Rust代碼組織的基本概念和實踐方法。

本文涉及的源碼可以在這里[8]下載。

4.5 參考資料

  • The book[9] - https://doc.rust-lang.org/book
  • The cargo book[10] - https://doc.rust-lang.org/cargo/index.html
  • The rustc book[11] - https://doc.rust-lang.org/rustc/index.html
責任編輯:武曉燕 來源: TonyBai
相關推薦

2024-04-22 08:06:34

Rust語言

2024-06-17 09:00:08

2024-06-07 08:59:35

2020-03-13 14:20:02

代碼開發 Rust

2023-06-19 14:14:24

Rust程序Web

2023-05-29 16:25:59

Rust函數

2024-11-08 09:19:28

2022-12-30 11:05:40

Rust代碼

2021-02-16 11:04:26

RustGo華為

2021-05-31 09:42:10

FuchsiaRust代碼

2023-05-14 18:56:50

Rust數據類型

2023-05-23 18:11:12

Rust數組元組

2025-01-03 09:12:11

2024-06-12 08:00:07

2021-03-19 08:58:19

Rust共享愿景文檔開發者

2021-02-24 07:42:34

PythonRust語言

2023-06-15 17:00:11

Rust循環

2024-01-18 15:24:06

Rust開發鴻蒙OH4.0

2025-02-27 00:00:15

2023-04-10 18:03:18

Rust編程語言
點贊
收藏

51CTO技術棧公眾號

av电影在线地址| 一区二区美女视频| 杨幂一区二区三区免费看视频| 欧美性xxxx在线播放| 亚洲精美视频| 亚洲伦理在线观看| 视频一区视频二区在线观看| 久久综合电影一区| 激情综合丁香五月| 国产一区二区三区| 色香蕉久久蜜桃| 佐佐木明希av| 国产污视频在线| 国产福利精品导航| 国产精品视频久久| 国产精品免费av一区二区| 成人影视亚洲图片在线| 精品久久人人做人人爽| jizz大全欧美jizzcom| 欧美性受ⅹ╳╳╳黑人a性爽| wwwwww.欧美系列| 99蜜桃在线观看免费视频网站| 无码人妻一区二区三区免费| 极品少妇一区二区三区| 日韩中文字幕网址| 亚洲天堂久久新| 成人午夜大片| 欧美一区二区二区| 91看片在线免费观看| www.综合| 亚洲成av人片在www色猫咪| 制服诱惑一区| av在线日韩国产精品| 久久综合资源网| 精品国产一区二区三区麻豆免费观看完整版 | 98精品国产高清在线xxxx天堂| av在线免费播放网址| 精品一区免费| 日韩激情av在线免费观看| 日本xxxx免费| 国产精品国产三级在线观看| 欧美午夜精品理论片a级按摩| 97在线播放视频| 高潮在线视频| 五月婷婷综合在线| 国产妇女馒头高清泬20p多| 亚洲男同gay网站| 亚洲丝袜制服诱惑| 国产精品99久久久久久大便| 午夜免费播放观看在线视频| 国产日韩欧美亚洲| 日本一区高清不卡| 国产在线观看免费网站| 久久久久久久久久久黄色| 免费亚洲精品视频| 久久久久久久久亚洲精品| 91亚洲国产成人精品一区二区三 | 亚洲一级免费在线观看| 日韩不卡在线| 欧美日韩一区国产| 三区视频在线观看| 永久免费精品视频| 精品国产91洋老外米糕| 久久久久亚洲AV成人无码国产| caoporn成人| 亚洲精品97久久| 91成人破解版| 日韩成人a**站| 久久久精品视频在线观看| 男人的天堂久久久| 韩日成人av| 欧美有码在线观看| 亚洲av无码不卡| 精品中文av资源站在线观看| 91视频88av| 欧日韩在线视频| 久久久另类综合| 一区二区三区四区视频在线| 中中文字幕av在线| 婷婷开心久久网| 日本999视频| 国产电影一区二区| 亚洲精品大尺度| 无码人妻aⅴ一区二区三区69岛| 日韩欧美中文| 欧美精品激情视频| 精品人妻一区二区三区潮喷在线| 久久国产精品露脸对白| av日韩中文字幕| 国产视频精品久久| 亚洲日本护士毛茸茸| 久久久亚洲精品无码| 成人免费福利| 日韩一区二区在线播放| theav精尽人亡av| 亚洲国产不卡| 欧美中文字幕视频| 99久久久久久久| 久久女同性恋中文字幕| 男插女免费视频| 欧美成人性网| 日韩欧美亚洲国产另类| mm131丰满少妇人体欣赏图| 一本精品一区二区三区| 欧美在线不卡区| 国产夫绿帽单男3p精品视频| 久久人人爽爽爽人久久久| 男同互操gay射视频在线看| 悠悠资源网亚洲青| 欧美一区二区三区电影| 国产伦精品一区二区三区视频女| 国产一区二区三区四区老人| 国产精品高清在线| 午夜视频免费在线| 亚洲欧美aⅴ...| 三级在线免费看| 另类图片第一页| 久久99视频免费| 一级特黄aaaaaa大片| 久久综合久久鬼色| 黄色大片中文字幕| 日韩精品久久久久久久软件91| 一区二区三区高清国产| 毛片毛片女人毛片毛片| 国产成+人+日韩+欧美+亚洲| 亚欧洲精品在线视频免费观看| 超碰成人av| 精品欧美久久久| 欧美做爰啪啪xxxⅹ性| 日韩精品每日更新| 欧美日本韩国一区二区三区| caoporn视频在线观看| 精品久久国产字幕高潮| 动漫性做爰视频| 狠狠网亚洲精品| 五月天婷亚洲天综合网鲁鲁鲁| 在线女人免费视频| 日韩国产高清污视频在线观看| 久久久久无码精品国产| 国产91丝袜在线播放九色| 91免费网站视频| 国产精品色婷婷在线观看| 中文字幕精品一区久久久久| www.亚洲激情| 国产欧美一区二区精品秋霞影院| 激情内射人妻1区2区3区 | 国产69精品久久99不卡| 热久久最新地址| 伊人久久噜噜噜躁狠狠躁| 欧美极品美女视频网站在线观看免费 | 青青草原av在线| 日韩精品在线看片z| 麻豆亚洲av成人无码久久精品| 国产精品538一区二区在线| 亚洲欧美日韩国产yyy | 欧美日韩国产综合视频在线观看| 免费一级黄色录像| 美腿丝袜亚洲一区| 免费观看黄色的网站| 精品国产亚洲一区二区三区大结局| 久久五月天色综合| 精品毛片在线观看| 天天色天天操综合| 国产交换配乱淫视频免费| 日日欢夜夜爽一区| 一区二区三区av| 国产精品亚洲一区二区在线观看| 欧美精品免费播放| 嫩草影院一区二区| 欧美丝袜第一区| 成人性生交大片免费看无遮挡aⅴ| 免费成人av资源网| 久久国产精品免费观看| 国产一区福利| 国产成人综合精品| 毛片网站在线免费观看| 欧美zozozo| jizz国产在线观看| 自拍偷拍亚洲激情| 国产精品成人无码专区| 三级久久三级久久久| 国产日本欧美在线| 欧美日韩精品一区二区三区在线观看| 日韩免费在线播放| av在线官网| 亚洲毛片在线看| 国产美女www爽爽爽视频| 亚洲国产sm捆绑调教视频 | 国产精品aaa| 麻豆91在线| 日韩精品视频免费专区在线播放| 一级黄色a视频| 亚洲成人av一区| www.涩涩爱| 99久久综合国产精品| 亚洲欧美日韩精品一区| 日韩视频精品在线观看| 中文字幕成人一区| 蜜臀av免费一区二区三区| 成人午夜黄色影院| 中文字幕 在线观看| 久久综合伊人77777尤物| 欧美拍拍视频| 日韩免费福利电影在线观看| 国产免费a视频| 亚洲成人中文在线| 国产高清视频免费在线观看| 久久午夜羞羞影院免费观看| 色黄视频免费看| 日韩精品高清不卡| 久激情内射婷内射蜜桃| 五月久久久综合一区二区小说| 久久久久久久久一区二区| 麻豆精品久久| 国产精品一香蕉国产线看观看| 国模私拍一区二区国模曼安| 久久天天躁狠狠躁夜夜躁| 可以免费看污视频的网站在线| 欧美成人乱码一区二区三区| 中国老头性行为xxxx| 欧美日韩在线视频观看| 久久黄色免费视频| 亚洲欧美另类小说| 在线观看日本黄色| 久久精品亚洲麻豆av一区二区 | 91美女精品网站| 在线视频国内自拍亚洲视频| 国产成人在线视频观看| 亚洲成av人影院在线观看网| 久久精品免费在线| 尤物av一区二区| 欧美做爰啪啪xxxⅹ性| 国产精品免费视频网站| 精品无码在线观看| 国产亲近乱来精品视频| 日韩精品无码一区二区三区久久久| av资源站一区| 国产乱了高清露脸对白| av亚洲产国偷v产偷v自拍| 69亚洲乱人伦| 成人激情视频网站| 男女性杂交内射妇女bbwxz| 成人动漫中文字幕| 亚洲 欧美 日韩在线| 99精品国产91久久久久久 | www三级免费| 日韩一级片网站| 成 人片 黄 色 大 片| 日韩免费高清av| 人妻一区二区三区| 日韩av中文字幕在线| 婷婷综合激情网| 精品亚洲aⅴ在线观看| 日本不卡视频一区二区| 亚洲天堂男人天堂| 午夜毛片在线| 九九久久综合网站| 超碰资源在线| 欧美在线视频网站| 韩日精品一区| 亚洲a在线观看| 国产精品久久久网站| 久久99精品久久久久久秒播放器 | 一级特黄录像免费播放全99| 亚洲乱码电影| 日韩欧美不卡在线| 久久久成人网| 色一情一区二区| 国产成人免费网站| 噜噜噜在线视频| 国产精品网站一区| 欧美激情国产精品免费| 午夜天堂影视香蕉久久| 国产精品熟女视频| 欧美日韩电影在线播放| 黄频网站在线观看| 亚洲欧洲午夜一线一品| 麻豆视频免费在线观看| 久久青草福利网站| 欧美日韩亚洲国产| 99久久伊人精品影院| 伊人久久大香线蕉av不卡| 亚洲自拍偷拍二区| 亚洲国产国产亚洲一二三| 国产理论在线播放| 国产成人精品午夜视频免费| 亚洲激情视频小说| 亚洲人成精品久久久久久| 91九色丨porny丨肉丝| 欧美日韩视频第一区| 欧美一级特黄aaaaaa| 一区二区三区亚洲| 超碰97国产精品人人cao| 国产精品91在线观看| 日本在线成人| 手机看片福利永久国产日韩| 在线观看视频免费一区二区三区| 无码少妇一区二区三区芒果| 国产成人自拍高清视频在线免费播放| 久久久久久久久免费看无码| 亚洲人成亚洲人成在线观看图片| 五月天婷婷久久| 日韩欧美二区三区| av大片在线观看| 2020欧美日韩在线视频| 欧美国产亚洲精品| 日韩一区二区三区高清| 一本色道久久综合| 中文字幕第一页在线视频| 久久久精品天堂| 日本在线小视频| 日韩免费观看高清完整版在线观看| 一本一道波多野毛片中文在线 | 视频欧美一区| 亚洲图片都市激情| 亚洲欧美清纯在线制服| 性高潮免费视频| 中文字幕在线视频一区| 欧美一区二区三区久久久| 亚洲精品第一页| 99re6在线精品视频免费播放| 91免费版网站入口| 欧美日韩第一| 日韩av一二三四| 久久午夜国产精品| 精品国产免费观看| 亚洲国产高潮在线观看| 啪啪免费视频一区| av色综合网| 欧美午夜视频| 欧美老女人bb| 亚洲制服欧美中文字幕中文字幕| 91久久国语露脸精品国产高跟| 最新69国产成人精品视频免费| 精品成人免费一区二区在线播放| 久久综合九色综合久99| 亚洲国内自拍| 99久久人妻无码中文字幕系列| 午夜精品久久久久久久久| 男人天堂网在线视频| 97精品在线视频| 欧美综合精品| 熟女少妇在线视频播放| 95精品视频在线| 五月激情六月丁香| 亚洲人成在线一二| 精品国产欧美日韩一区二区三区| 日韩精品久久一区| 男女性色大片免费观看一区二区| 精品日韩在线视频| 欧美人牲a欧美精品| 麻豆传媒在线完整视频| 亚洲影院高清在线| 亚洲网站在线| 女同毛片一区二区三区| 色诱视频网站一区| 在线观看麻豆| 亚洲自拍小视频| 精品动漫一区| 丰满圆润老女人hd| 欧美午夜视频网站| 欧美jizzhd欧美| 91黄色精品| 国产亚洲精品久久久久婷婷瑜伽| 国产精品亚洲无码| 欧美久久久久久久久久| 97超碰资源站在线观看| 精品视频在线观看| 视频一区国产视频| 国产精品久久久久久久精| 亚洲精品在线观看网站| 暖暖成人免费视频| 国产精品99久久久久久大便| 国产 欧美在线| 亚洲熟妇无码乱子av电影| 少妇精69xxtheporn| 日韩影片在线观看| 久久精品99国产| 日韩理论片中文av| 天天干天天舔天天射| 国产精品美女久久久免费 | 久久久久亚洲精品国产| 女人av一区| 日韩精品xxx| 色香蕉久久蜜桃| 天堂av中文在线| 人禽交欧美网站免费| 国v精品久久久网| 一级一级黄色片| 国内揄拍国内精品| 日韩成人a**站| 免费a在线观看播放| 欧美高清激情brazzers| h片在线观看视频免费| 亚洲精品高清国产一线久久| 不卡影院免费观看| 国产又大又粗又硬| 琪琪第一精品导航| 黄色另类av| 久草福利资源在线|