电商商城定制开发Rust 从入门到精通11-包和模块管理项目

电商商城定制开发当我们项目较小时,一个 main.rs 电商商城定制开发文件就能搞定所有。电商商城定制开发但是如果项目较大,电商商城定制开发功能较多时,电商商城定制开发就很难搞定了。电商商城定制开发我们需要对相关功能进电商商城定制开发行分组和划分不同功能的代码,电商商城定制开发这样编写和维护都会比较方便。

电商商城定制开发如果你是一个Java程序员,电商商城定制开发相信你一定用过这样几个东西。电商商城定制开发首先项目较大,依赖较多,电商商城定制开发我们通常会使用maven/gradle 电商商城定制开发等工具进行依赖管理,电商商城定制开发然后将各个功能划分到不同的 package 中,比如(controller/service/dao/domain/utils等等)。

那么 电商商城定制开发程序员该如何管理大型项目呢?

①、Cargo: Rust 电商商城定制开发的包管理工具,电商商城定制开发能够管理外部依赖以及进行项目的编译和测试 create;

②、package: Cargo 提供的功能,一个包会含有一个 Cargo.toml 文件,是提供一系列功能的一个或多个 create。

③、create: 表示项目,是 Rust 中的独立编译单元。每个 create 对应生成一个库或可执行文件(.lib/.dll/.so/.exe)。

④、模块Modules)和 use: 允许你控制和路径的私有性。

⑤、路径path):为 struct、function 或 module 等项命名的方式。

PS:其实这么多名词核心问题就是如何管理作用域,我们代码中的变量、方法,开发者如何调用?又或者能够调用哪些?编译器如何去找?

1、Cargo

Cargo 是Rust 的包管理工具,并不是一个编译器。

Rust 的编译器是 rustc

我们使用 Cargo 编译工程实际上还是调用 rustc 来完成的。如果我们想知道 cargo 在后面是如何调用 rustc 完
成编译的,可以使用 cargo build --verbose 选项查看详细的编译命令。

常用的 Cargo 命令:

一、cargo -h : 帮助命令

二、cargo new: 创建项目

三、cargo build: 编译项目

四、cargo run: 运行项目

五、cargo check: 只检查编译错误,而不做代码优化以及生成可执行程序,非常适合在开发过程中快速检查语法、类型错误。

六、cargo clean: 清理以前编译的结果。

七、cargo doc: 生成该项目的文档。

八、cargo test: 执行单元测试。

九、cargo bench: 执行 benchmark 性能测试。

十、cargo update: 升级所有依赖项的版本,重新生成 Cargo.lock 文件。

十一、cargo install: 安装可执行程序。这个命令非常有用,可以扩展 cargo 的子命令,为它增加新的功能。比如 可以使用 cargo install cargo-tree 命令,然后通过 cargo tree 打印依赖项的树形结构。

十二、cargo uninstall: 卸载可执行程序。

2、package 和 create

create 的作用:将相关功能组合到一个作用域内,便于在项目间进行共享,防止冲突。

①、crate 是一个二进制项(binary)或者库(library)。

②、crate root 是一个源文件,Rust 编译器以它为起始点,并构成你的 crate 的根模块(module)。

③、包*(package) 是提供一系列功能的一个或者多个 crate。一个包会包含有一个 Cargo.toml 文件,阐述如何去构建这些 crate。

包中可以包含至多一个库 crate(library crate)。包中可以包含任意多个二进制 crate(binary crate),但是必须至少包含一个 crate(无论是库的还是二进制的)。

下面我们通过 Cargo 创建一个包。

$ cargo new my-project     Created binary (application) `my-project` package$ ls my-projectCargo.tomlsrc$ ls my-project/srcmain.rs
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

可以看到Cargo 会给我们的包创建一个 Cargo.toml 文件,查看内容如下:

[package]name = "my-project"version = "0.1.0"edition = "2021"# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[dependencies]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

我们会发现并没有提到 src/main.rs,因为 Cargo 遵循的一个约定:src/main.rs 就是一个与包同名的二进制 crate 的 crate 根。

同样的,Cargo 知道如果包目录中包含 src/lib.rs,则包带有与其同名的库 crate,且 src/lib.rs 是 crate 根。crate 根文件将由 Cargo 传递给 rustc 来实际构建库或者二进制项目。

在此,我们有了一个只包含 src/main.rs 的包,意味着它只含有一个名为 my-project 的二进制 crate。如果一个包同时含有 src/main.rssrc/lib.rs,则它有两个 crate:一个二进制的和一个库的,且名字都与包相同。

通过将文件放在 src/bin 目录下,一个包可以拥有多个二进制 crate:每个 src/bin 下的文件都会被编译成一个独立的二进制 crate。

3、module

模块 的作用:

①、在一个 crate 中,将代码进行分组,以提高可读性与重用性。

②、控制项目的访问权限(private/public),默认是私有(private)。

3.1 创建 mod

// 前台mod front_of_house {    mod hosting {        fn add_to_waitlist() {}        fn seat_at_table() {}    }    mod serving {        fn take_order() {}        fn serve_order() {}        fn take_payment() {}    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

我们在 src 目录下创建了一个 lib.rs 文件,然后在里面添加上面的代码。

这里面我们定义一个模块,是以 mod 关键字为起始,然后指定模块的名字(这里叫做 front_of_house),并且用花括号包围模块的主体。在模块内,我们还可以定义其他的模块,就像本例中的 hostingserving 模块。模块还可以保存一些定义的其他项,比如结构体、枚举、常量、特性、或者函数。

通过使用模块,我们可以将相关的定义分组到一起,并指出他们为什么相关。程序员可以通过使用这段代码,更加容易地找到他们想要的定义,因为他们可以基于分组来对代码进行导航,而不需要阅读所有的定义。程序员向这段代码中添加一个新的功能时,他们也会知道代码应该放置在何处,可以保持程序的组织性。

这个模块的树形结构如下:

crate └── front_of_house     ├── hosting     │   ├── add_to_waitlist     │   └── seat_at_table     └── serving         ├── take_order         ├── serve_order         └── take_payment
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

4、path

上一节我们定义了模块以及如何创建模块,那么如何访问模块中某一项呢?

这就需要使用路径(path),就像在文件系统使用路径一样。如果我们想要调用一个函数,我们需要知道它的路径。

4.1 绝对路径和相对路径

  • 绝对路径absolute path)从 crate 根开始,以 crate 名或者字面值 crate 开头。
  • 相对路径relative path)从当前模块开始,以 selfsuper 或当前模块的标识符开头。

绝对路径和相对路径后都跟一个或多个由双冒号(::)分割的标识符。

// 前台模块mod front_of_house {    mod hosting {        fn add_to_waitlist() {}    }}pub fn eat_at_restaurant() {    // 绝对路径    crate::front_of_house::hosting::add_to_waitlist();    // 相对路径    front_of_house::hosting::add_to_waitlist();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

PS:建议使用绝对路径,这样代码调用位置移动也不用修改。

4.2 使用 pub 关键字控制访问权限

上面的代码,我们编译,会报如下错误:

这是因为:

①、 Rust 中默认所有项(函数、方法、结构体、枚举、模块和常量)都是私有的。

②、父模块中的项不能使用子模块中的私有项,但是子模块中的项可以使用他们父模块中的项。

为了让上面的代码编译通过,我们可以使用关键字 pub 将 front_of_house 模块下的 hosting 模块定义为公共的。

mod front_of_house {    pub mod hosting {        fn add_to_waitlist() {}    }}
  • 1
  • 2
  • 3
  • 4
  • 5

我们接着编译,发现还是报错:

看报错是因为 add_to_waitlist() 方法是私有的,我们只是给其父模块增加了 pub 关键字,这说明:

通过关键字 pub 使其模块公有,但是其内容默认还是私有的。

我们需要将 add_to_waitlist() 方法也加上 pub 关键字,才会编译通过。

4.3 使用 super 关键字表示父模块路径

super 关键字表示父模块路径,类似文件系统中的 .. 开头的语法。

fn serve_order() {}mod back_of_house {    fn fix_incorrect_order() {        cook_order();        super::serve_order();    }    fn cook_order() {}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4.4 使用 use 关键字将路径引入作用域

前面例子中无论我们选择 add_to_waitlist 函数的绝对路径还是相对路径,每次我们想要调用 add_to_waitlist 时,都必须指定front_of_househosting

pub fn eat_at_restaurant() {    // 绝对路径    crate::front_of_house::hosting::add_to_waitlist();    // 相对路径    self::front_of_house::hosting::add_to_waitlist();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

那么有没有办法简化这个路径呢?

答案是:我们可以使用 use 关键字将路径一次性引入作用域,然后调用该路径中的项,就如同它们是本地项一样。

mod front_of_house {    pub mod hosting {        pub fn add_to_waitlist() {}    }}use crate::front_of_house::hosting;pub fn eat_at_restaurant() {    hosting::add_to_waitlist();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

另外,use 也可以引用相对路径,并且也会检查路径私有性。

4.5 使用 as 关键字提供新的名称

as 关键字可以为引入的路径指定本地别名。

比如使用 use 将两个同名类型引入同一作用域,调用的时候就必须带上父路径,如果不想带上,可以给这两个同名类型起一个别名。

use std::fmt::Result;use std::io::Result as IoResult;fn function1() -> Result {    // --snip--    Ok(())}fn function2() -> IoResult<()> {    // --snip--    Ok(())}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

4.6 使用 pub use 重新导出名称

使用 use 关键字,将某个名称导入当前作用域后,这个名称在此作用域中就可以使用了,但它对此作用域之外还是私有的。

如果想让其他人调用我们的代码时,也能够正常使用这个名称,就好像它本来就在当前作用域一样,那我们可以将 pubuse 合起来使用。这种技术被称为 “重导出re-exporting)”:我们不仅将一个名称导入了当前作用域,还允许别人把它导入他们自己的作用域。

mod front_of_house {    pub mod hosting {        pub fn add_to_waitlist() {}    }}pub use crate::front_of_house::hosting;pub fn eat_at_restaurant() {    hosting::add_to_waitlist();    hosting::add_to_waitlist();    hosting::add_to_waitlist();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

5、引入外部依赖

在Java项目中,我们引入外部依赖通常是在 pom.xml 文件中引入外部依赖。

在 rust 中,我们是在 Cargo.toml 文件中引入,比如引入一个随机数依赖:

rand = "0.8.3"
  • 1

Cargo.toml 中加入 rand 依赖告诉了 Cargo 要从 下载 rand 和其依赖,并使其可在项目代码中使用。

接着,为了将 rand 定义引入项目包的作用域,我们加入一行 use 起始的包名,它以 rand 包名开头并列出了需要引入作用域的项。

use std::io;use rand::Rng;fn main() {    println!("Guess the number!");    let secret_number = rand::thread_rng().gen_range(1..=100);    println!("The secret number is: {secret_number}");    println!("Please input your guess.");    let mut guess = String::new();    io::stdin()        .read_line(&mut guess)        .expect("Failed to read line");    println!("You guessed: {guess}");}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

6、嵌套路径来消除大量的use 行

当需要引入很多定义于相同包或相同模块的项时,可以进行合并。

①、比如:

use std::cmp::Ordering;use std::io;
  • 1
  • 2

可以改写为:

use std::{cmp::Ordering, io};
  • 1

②、层级共享

use std::io;use std::io::Write;
  • 1
  • 2

可以改写为:

use std::io::{self, Write};
  • 1

7、通过 * 将所有的公有定义引入作用域

如果希望将一个路径下 所有 公有项引入作用域,可以指定路径后跟 *,glob 运算符:

use std::collections::*;
  • 1

这个 use 语句将 std::collections 中定义的所有公有项引入当前作用域。

使用 * 运算符时要注意:这会使得我们难以推导作用域中有什么名称和它们是在何处定义的。

glob 运算符经常用于测试模块 tests 中,这时会将所有内容引入作用域。

用于预导入(prelude)模块。

8、将模块拆分成多个文件

当模块变多时(多个方法、结构体等),我们需要将它们的定义移动到单独的文件中,从而使代码更容易阅读。

这里需要注意两个点:

①、模块定义时,如果模块名后面是“;”,而不是代码块,那么rust 会从与模块同名的文件中加载内容。

②、模块树的结构不会变化。

比如,我们在 lib.rs 创建如下内容:

// 前台模块mod front_of_house {    pub mod hosting {       pub fn add_to_waitlist() {       }    }}pub fn eat_at_restaurant() {    // 绝对路径    crate::front_of_house::hosting::add_to_waitlist();    // 相对路径    self::front_of_house::hosting::add_to_waitlist();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

现在,我们想把 font_of_house 模块移动出去,需要进行两步操作:

一、在 src 目录下创建 font_of_house.rs 文件,内容如下:

pub mod hosting {    pub fn add_to_waitlist() {    }}
  • 1
  • 2
  • 3
  • 4

二、lib.rs 改写

// 前台模块mod front_of_house;pub fn eat_at_restaurant() {    // 绝对路径    crate::front_of_house::hosting::add_to_waitlist();    // 相对路径    self::front_of_house::hosting::add_to_waitlist();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

同理,如果我们还想把 hosting 里面的模块也提取出去,那该怎么办呢?

假设,我们直接在 src 目录下创建 hosting.rs 文件,然后看看编译结果:

这对应了我们前面说的模块树的结构是不变的,所以编译器是找 src/font_of_house 目录下的 hosting.rs 文件,我们不能将其放在 src 目录下。

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发