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

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