You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

11 KiB

使用package,crates和modules管理项目

Rust 具有许多功能,可让您管理代码的组织,包括公开哪些细节,哪些细节是私有的,以及程序中每个范围内的名称。这些功能有时统称为模块系统,包括:

  • Packages: Cargo 的一项功能,可让您构建、测试和共享 crate
  • **Crates:**生成库或可执行文件的模块树
  • Modules and **use:**让您控制路径的组织、范围和隐私
  • **Paths:**一种命名项目的方式,例如结构、函数或模块

packages and crates

模块是 Rust 编译器在同一时间考虑的最小代码单元。即使你使用 rustc 而不是 cargo,并且仅传递一个源代码文件,编译器也会将该文件视为模块。模块可以包含其他模块,并且这些模块可以在随同模块一起编译的其他文件中定义。

模块可以以两种形式之一的形式出现:二进制模块或库模块。二进制模块是你可以编译成可执行文件的程序,例如命令行程序或服务器。每个二进制模块必须有一个名为 main 的函数,它定义了可执行文件运行时发生的事情。我们迄今为止创建的所有模块都是二进制模块。

库模块没有 main 函数,也不能编译成可执行文件。相反,它们定义的是旨在与多个项目共享的功能。例如,我们在第 2 章中使用的 rand 模块提供了生成随机数的功能。大多数时候,当 Rust 程序员说“模块”时,他们指的是库模块,并且将“模块”和一般的编程概念“库”交替使用。

包则是一组相关的模块的集合。包中的所有模块都共享同一个名称空间。包通常由多个文件组成,这些文件可能位于多个目录中。包还可以依赖于其他包,这意味着它们可以使用其他包中定义的功能。

在 Rust 中,每个文件都属于一个模块,而每个目录都属于一个包。在同一包中的所有文件都共享同一个名称空间。这意味着如果一个文件定义了一个函数或变量,那么这个函数或变量就可以在同一包中的其他文件中使用。

模块的根目录是 Rust 编译器从中启动的源文件,并构成了你的模块的根模块(我们将在“定义模块以控制作用域和隐私”一节中详细解释模块)。

包是一个或多个模块的集合,它提供了一组功能。包包含一个 Cargo.toml 文件,该文件描述了如何构建这些模块。Cargo 实际上是一个包,其中包含了你一直在使用的命令行工具的二进制模块。Cargo 包还包含了一个库模块,二进制模块依赖于此库模块。其他项目也可以依赖于 Cargo 库模块,以使用与 Cargo 命令行工具使用的相同逻辑。

包可以包含任意数量的二进制模块,但至多只能包含一个库模块(因为包的目的是提供一组功能,而不是多个不同的功能集。)。包必须至少包含一个模块,无论是库模块还是二进制模块。

image-20230110095230620

使用 ls 查看 Cargo 创建了什么。在项目目录中,有一个 Cargo.toml 文件,提供了一个包。还有一个 src 目录,其中包含 main.rs。如果一个包只包含 src/main.rs,则意味着它只包含一个名为 my-project 的二进制模块。这意味着这个包只包含一个可执行文件,该文件的名称与包的名称相同(在这种情况下为 my-project)。

如果一个包同时包含 src/main.rssrc/lib.rs,则它有两个模块:一个名为 my-project 的二进制模块和一个同名的库模块。这意味着这个包包含一个可执行文件和一个

包可以通过将文件放在 src/bin 目录中拥有多个二进制模块:每个文件将是一个单独的二进制模块。这意味着你可以在同一个包中提供多个可执行文件,每个文件都有自己的名称。在这种情况下,你可以使用 Cargo 进行构建,并指定要构建哪个二进制模块。

一个包下多个二进制模块,一个库模块:

image-20230110101632310

运行方式:

image-20230110101543444

多个二进制模块时运行main

如果你的包中包含多个二进制模块,或者你希望运行一个名称不为 main.rs 的二进制模块,那么你需要指定二进制模块的名称。你可以使用 cargo build --bin 和 cargo run --bin 命令,其中 bin 后面跟着二进制模块的名称。

image-20230110102314287

定义模块控制范围和隐私

模块系统是 Rust 中用来组织代码的一种机制。在本节中,我们将讨论模块以及模块系统的其他部分,包括允许你命名项目的路径;将路径引入作用域的 use 关键字;以及使项目变为公有的 pub 关键字。我们还将讨论 as 关键字、外部包和 glob 操作符。

crate root 通常是 src/lib.rs;对于二进制 crate,crate root 通常是 src/main.rs。如果你的包同时包含了 src/lib.rs 和 src/main.rs 两个文件,那么 src/lib.rs 是库模块的 crate root,src/main.rs 是二进制模块的 crate root。当你使用 cargo build 或者 cargo run 编译和运行这个包时,cargo 会根据你的命令来选择编译哪个 crate root。如果你使用 cargo build,cargo 会编译 src/lib.rs;如果你使用 cargo run,cargo 会编译 src/main.rs。

模块备忘录

以下提供了关于模块、路径、use 关键字和 pub 关键字在编译器中的工作方式,以及大多数开发人员如何组织代码的快速参考。

  1. Start from the crate root: 编译crate时,编译器首先在crate根文件中查找要编译的代码(库crate一般是src/lib.rs, 对于二进制crate通常是src/main.rs)。

  2. Declaring modules: 在crate根文件中,你可以声明新的模块。比如可以用 **mod garden;**来声明一个“garden”模块。编译器会在以下位置选择模块的代码: 1. 直接在mod garden后面定义,使用花括号替代分号。 1. 在文件src/garden.rs中 1. 在文件src/garden/mod.rs中。mod.rs 文件是 Rust 中的一种特殊文件。它允许你在一个文件夹中组织你的模块定义,并且在外部看起来就像一个普通的模块一样。使代码更容易阅读和维护。

  3. Declaring submodules: 在crate root以外的任何文件中,都可以声明子模块。例如,你可以在src/garden.rs中声明 **mod vegetables; **编译器将在以父模块命名的目录中查找子模块代码:

    1. 直接在mod vegetable后面,使用花括号替代分号。
    2. 文件src/garden/vegetables.rs
    3. 文件src/garden/vegetables/mod.rs
  4. Paths to code in modules: 当一个模块成为你的 crate 的一部分时,你可以在 crate 中的任何地方引用该模块中的代码,只要隐私规则允许,使用该代码的路径。例如,在 garden vegetables 模块中的 Asparagus 类型将在 crate::garden::vegetables::Asparagus 处找到。

  5. Private vs public: 默认情况下,模块内的代码是不能被它的父模块访问的。如果想让模块可以被父模块访问,就需要在模块定义时使用 pub mod 而不是 mod。如果想让公共模块内的项也可以被其他地方访问,需要在项的定义前加上 pub 关键字。

  6. The use keyword: 在一个范围中,使用关键字可以为项目创建快捷方式,以减少长路径的重复。在任何可以引用crate::garden::vegetables::Asparagus的范围中,您可以使用 use crate::garden::vegetables::Asparagus; 创建一个快捷方式,从此以后,只需要编写 Asparagus 即可在该范围中使用该类型。

引用其他模块:

image-20230110152746991

vegetables.rs代码:

#[derive(Debug)]
pub struct Asparagus {}

pub fn hello_world(){
    println!("hello world vegetables!!");
}

garden.rs代码:

pub mod vegetables;

pub fn hello_world(){
    println!("hello garden");
}

main.rs代码:

use crate::garden::vegetables::Asparagus;

pub mod garden;

fn main() {
    let plant = Asparagus{};

    garden::hello_world();
    crate::garden::vegetables::hello_world();
    garden::vegetables::hello_world();
    println!("I'm growing {:?}!", plant);

}

image-20230110152650259

bin目录一般用于存放可执行文件,也就是项目中的主程序。这些文件都是独立的可执行文件,不需要其它文件的支持。

如果你把garden.rs放在bin目录下,那么garden.rs就不是项目的crate root了,因此在编译时不会被编译,也就不能在main中直接使用了。你需要将garden.rs中的代码移动到src目录下,并修改mod garden引入路径。指定cargo run --bin backyard 是为了运行bin目录下的main.rs。

故而在garden.rs写main函数永远不会执行,src/main.rs是默认的crate root文件。

使用mod.rs的好处。

image-20230110113332025

将相关代码分组到模块中

在Rust中,模块(module)提供了一种将代码在crate中进行组织和重复使用的方式。模块还允许我们控制项目的隐私,因为模块内的代码默认是私有的。私有项目是外部代码不能使用的实现细节。我们可以选择将模块和其中的项目公开,这样就可以将它们暴露给外部代码使用和依赖。

餐厅库的定义:

image-20230110162401871

前台模块的实现:

image-20230110164745830

我们使用关键字 mod 和模块的名称(在这种情况下是 front_of_house)来定义一个模块。然后将模块的正文放在大括号内。在模块中,我们可以放其他模块,如在这种情况下的 hosting 和 serving 模块。模块还可以包含其他项目的定义,如结构体、枚举、常量、特征,以及函数。

通过使用模块,我们可以将相关的定义分组并命名它们的关系。使用此代码的程序员可以根据组来导航代码,而不是必须阅读所有定义,这样可以更容易地找到对他们有关的定义。向此代码添加新功能的程序员将知道将代码放置在哪里以保持程序的组织性。

先前,我们提到 src/main.rs 和 src/lib.rs 称为 crate roots。它们名称的原因是这两个文件中的内容形成了一个名为 crate 的模块,该模块位于 crate 模块结构的根部,被称为模块树。请注意,整个模块树都以名为 crate 的隐式模块为根。

crate
 └── front_of_house
     ├── hosting
        ├── add_to_waitlist
        └── seat_at_table
     └── serving
         ├── take_order
         ├── serve_order
         └── take_payment