• Packages): Cargo 的一个功能,它允许你构建、测试和分享 crate。
  • Crates :一个模块的树形结构,它形成了库或二进制项目。
  • 模块Modules)和 use: 允许你控制作用域和路径的私有性。
  • 路径path):一个命名例如结构体、函数或模块等项的方式

包和crates

package) 是提供一系列功能的一个或者多个 crate。一个包会包含有一个 Cargo.toml 文件。

包中所包含的内容由几条规则来确立。一个包中至多 只能 包含一个库 crate(library crate);包中可以包含任意多个二进制 crate(binary crate);包中至少包含一个 crate,无论是库的还是二进制的。
  1. $ cargo new my-project
  2. Created binary (application) `my-project` package
  3. $ ls my-project
  4. Cargo.toml
  5. src
  6. $ ls my-project/src
  7. main.rs

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

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

定义模块来控制作用域与私有性

模块 让我们可以将一个 crate 中的代码进行分组,以提高可读性与重用性。模块还可以控制项的 私有性,即项是可以被外部代码使用的(public),还是作为一个内部实现的内容,不能被外部代码使用(private)。

通过执行 cargo new —lib restaurant,来创建一个新的名为 restaurant 的库。
  1. mod front_of_house {
  2. mod hosting {
  3. fn add_to_waitlist() {}
  4. fn seat_at_table() {}
  5. }
  6. mod serving {
  7. fn take_order() {}
  8. fn server_order() {}
  9. fn take_payment() {}
  10. }
  11. }
  1. crate
  2. └── front_of_house
  3. ├── hosting
  4. ├── add_to_waitlist
  5. └── seat_at_table
  6. └── serving
  7. ├── take_order
  8. ├── serve_order
  9. └── take_payment

路径用于引用模块树中的项

  • 绝对路径absolute path)从 crate 根开始,以 crate 名或者字面值 crate 开头。
  • 相对路径relative path)从当前模块开始,以 selfsuper 或当前模块的标识符开头。
绝对路径和相对路径都后跟一个或多个由双冒号(::)分割的标识符。
  1. mod front_of_house {
  2. mod hosting {
  3. fn add_to_waitlist() {}
  4. }
  5. }
  6. pub fn eat_at_restaurant() {
  7. // Absolute path
  8. crate::front_of_house::hosting::add_to_waitlist();
  9. // Relative path
  10. front_of_house::hosting::add_to_waitlist();
  11. }
  12. // cargo build error
错误信息说 hosting 模块是私有的。换句话说,我们拥有 hosting 模块和 add_to_waitlist 函数的的正确路径,但是 Rust 不让我们使用,因为它不能访问私有片段。 Rust 中默认所有项(函数、方法、结构体、枚举、模块和常量)都是私有的。父模块中的项不能使用子模块中的私有项,但是子模块中的项可以使用他们父模块中的项。

使用 pub 关键字暴露路径

  1. mod front_of_house {
  2. pub mod hosting {
  3. fn add_to_waitlist() {}
  4. }
  5. }
  6. pub fn eat_at_restaurant() {
  7. // Absolute path
  8. crate::front_of_house::hosting::add_to_waitlist();
  9. // Relative path
  10. front_of_house::hosting::add_to_waitlist();
  11. }
  12. // error
  13. // hosting 的 内容(contents) 仍然是私有的;这表明使模块公有并不使其内容也是公有的。
  14. // 模块上的 pub 关键字只允许其父模块引用它。
dd_to_waitlist 函数是私有的。私有性规则不但应用于模块,还应用于结构体、枚举、函数和方法。
  1. mod front_of_house {
  2. pub mod hosting {
  3. pub fn add_to_waitlist() {}
  4. }
  5. }
  6. pub fn eat_at_restaurant() {
  7. // Absolute path
  8. crate::front_of_house::hosting::add_to_waitlist();
  9. // Relative path
  10. front_of_house::hosting::add_to_waitlist();
  11. }
  12. // true
front_of_house 模块不是公有的,不过因为 eat_at_restaurant 函数与 front_of_house 定义于同一模块中(即,eat_at_restaurantfront_of_house 是兄弟),我们可以从 eat_at_restaurant 中引用 front_of_house

使用 super 起始的相对路径

我们还可以使用 super 开头来构建从父模块开始的相对路径。这么做类似于文件系统中以 .. 开头的语法。
  1. fn serve_order() {}
  2. mod back_of_house {
  3. fn fix_incorrect_order() {
  4. cook_order();
  5. super::serve_order();
  6. }
  7. fn cook_order() {}
  8. }

创建公有的结构体和枚举

我们定义了一个公有结构体 back_of_house:Breakfast,其中有一个公有字段 toast 和私有字段 seasonal_fruit
  1. mod back_of_house {
  2. pub struct Breakfast {
  3. pub toast: String,
  4. seasonal_fruit: String,
  5. }
  6. impl Breakfast {
  7. pub fn summer(toast: &str) -> Breakfast {
  8. Breakfast {
  9. toast: String::from(toast),
  10. seasonal_fruit: String::from("peaches"),
  11. }
  12. }
  13. }
  14. }
  15. pub fn eat_at_restaurant() {
  16. // Order a breakfast in the summer with Rye toast
  17. let mut meal = back_of_house::Breakfast::summer("Rye");
  18. // Change our mind about what bread we'd like
  19. meal.toast = String::from("Wheat");
  20. println!("I'd like {} toast please", meal.toast);
  21. // The next line won't compile if we uncomment it; we're not allowed
  22. // to see or modify the seasonal fruit that comes with the meal
  23. // meal.seasonal_fruit = String::from("blueberries");
  24. }
因为 back_of_house::Breakfast 具有私有字段,所以这个结构体需要提供一个公共的关联函数来构造 Breakfast 的实例(这里我们命名为 summer)。如果 Breakfast 没有这样的函数,我们将无法在 eat_at_restaurant 中创建 Breakfast 实例,因为我们不能在 eat_at_restaurant 中设置私有字段 seasonal_fruit 的值。 与之相反,如果我们将枚举设为公有,则它的所有成员都将变为公有。
  1. mod back_of_house {
  2. pub enum Appetizer {
  3. Soup,
  4. Salad,
  5. }
  6. }
  7. pub fn eat_at_restaurant() {
  8. let order1 = back_of_house::Appetizer::Soup;
  9. let order2 = back_of_house::Appetizer::Salad;
  10. }

使用 use 关键字将名称引入作用域

我们可以使用 use 关键字将路径一次性引入作用域,然后调用该路径中的项,就如同它们是本地项一样。
  1. mod front_of_house {
  2. pub mod hosting {
  3. pub fn add_to_waitlist() {}
  4. }
  5. }
  6. use crate::front_of_house::hosting;
  7. pub fn eat_at_restaurant() {
  8. hosting::add_to_waitlist();
  9. hosting::add_to_waitlist();
  10. hosting::add_to_waitlist();
  11. }
在作用域中增加 use 和路径类似于在文件系统中创建软连接(符号连接,symbolic link)。通过在 crate 根增加 use crate::front_of_house::hosting,现在 hosting 在作用域中就是有效的名称了,如同 hosting 模块被定义于 crate 根一样。
  1. mod front_of_house {
  2. pub mod hosting {
  3. pub fn add_to_waitlist() {}
  4. }
  5. }
  6. use front_of_house::hosting;
  7. pub fn eat_at_restaurant() {
  8. hosting::add_to_waitlist();
  9. hosting::add_to_waitlist();
  10. hosting::add_to_waitlist();
  11. }

创建惯用的 use 路径

  1. mod front_of_house {
  2. pub mod hosting {
  3. pub fn add_to_waitlist() {}
  4. }
  5. }
  6. use crate::front_of_house::hosting::add_to_waitlist;
  7. pub fn eat_at_restaurant() {
  8. add_to_waitlist();
  9. add_to_waitlist();
  10. add_to_waitlist();
  11. }
这并不符合习惯 要想使用 use 将函数的父模块引入作用域,我们必须在调用函数时指定父模块,这样可以清晰地表明函数不是在本地定义的,同时使完整路径的重复度最小化。 另一方面,使用 use 引入结构体、枚举和其他项时,习惯是指定它们的完整路径。
  1. use std::collections::HashMap;
  2. fn main() {
  3. let mut map = HashMap::new();
  4. map.insert(1, 2);
  5. }
这个习惯用法有一个例外,那就是我们想使用 use 语句将两个具有相同名称的项带入作用域,因为 Rust 不允许这样做。
  1. use std::fmt;
  2. use std::io;
  3. fn function1() -> fmt::Result {
  4. // --snip--
  5. }
  6. fn function2() -> io::Result<()> {
  7. // --snip--
  8. }
使用父模块可以区分这两个 Result 类型。

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

使用 use 将两个同名类型引入同一作用域这个问题还有另一个解决办法:在这个类型的路径后面,我们使用 as 指定一个新的本地名称或者别名。
  1. use std::fmt::Result;
  2. use std::io::Result as IoResult;
  3. fn function1() -> Result {
  4. // --snip--
  5. }
  6. fn function2() -> IoResult<()> {
  7. // --snip--
  8. }

使用 pub use 重导出名称

当使用 use 关键字将名称导入作用域时,在新作用域中可用的名称是私有的。如果为了让调用你编写的代码的代码能够像在自己的作用域内引用这些类型,可以结合 pubuse。这个技术被称为 “重导出re-exporting)”,因为这样做将项引入作用域并同时使其可供其他代码引入自己的作用域。
  1. mod front_of_house {
  2. pub mod hosting {
  3. pub fn add_to_waitlist() {}
  4. }
  5. }
  6. pub use crate::front_of_house::hosting;
  7. pub fn eat_at_restaurant() {
  8. hosting::add_to_waitlist();
  9. hosting::add_to_waitlist();
  10. hosting::add_to_waitlist();
  11. }
通过 pub use,现在可以通过新路径 hosting::add_to_waitlist 来调用 add_to_waitlist 函数。如果没有指定 pub useeat_at_restaurant 函数可以在其作用域中调用 hosting::add_to_waitlist,但外部代码则不允许使用这个新路径。

https://stackoverflow.com/questions/69275034/what-is-the-difference-between-use-and-pub-use

  1. // src/foo/mod.rs
  2. mod bar;
  3. pub use bar::item;
  4. // src/foo/bar.rs
  5. pub fn item() {
  6. println!("Hello, world!");
  7. }
  8. // src/main.rs
  9. mod foo;
  10. use foo::item;
  11. fn main() {
  12. item();
  13. }

使用外部包

  1. [dependencies]
  2. rand = "0.5.5"
Cargo.toml 中加入 rand 依赖告诉了 Cargo 要从 crates.io 下载 rand 和其依赖,并使其可在项目代码中使用。
  1. use rand::Rng;
  2. fn main() {
  3. let secret_number = rand::thread_rng().gen_range(1, 101);
  4. }
注意标准库(std)对于你的包来说也是外部 crate。因为标准库随 Rust 语言一同分发,无需修改 Cargo.toml 来引入 std,不过需要通过 use 将标准库中定义的项引入项目包的作用域中来引用它们,比如我们使用的 HashMap
  1. use std::collections::HashMap;
这是一个以标准库 crate 名 std 开头的绝对路径。 std是外部库的cargo根 自己文件则是以cargo名为根

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

当需要引入很多定义于相同包或相同模块的项时,为每一项单独列出一行会占用源码很大的空间。
  1. use std::cmp::Ordering;
  2. use std::io;
  3. // ---snip---
  4. use std::{cmp::Ordering, io};
  5. // ---snip---
  6. use std::io;
  7. use std::io::Write;
  8. use std::io::{self, Write};

通过 glob 运算符将所有的公有定义引入作用域

如果希望将一个路径下 所有 公有项引入作用域,可以指定路径后跟 (*)glob 运算符:
  1. use std::collections::*;
这个 use 语句将 std::collections 中定义的所有公有项引入当前作用域。使用 glob 运算符时请多加小心!Glob 会使得我们难以推导作用域中有什么名称和它们是在何处定义的。

将模块分割进不同文件

到目前为止,所有的例子都在一个文件中定义多个模块。当模块变得更大时,你可能想要将它们的定义移动到单独的文件中,从而使代码更容易阅读。
  1. mod front_of_house;
  2. pub use crate::front_of_house::hosting;
  3. pub fn eat_at_restaurant() {
  4. hosting::add_to_waitlist();
  5. hosting::add_to_waitlist();
  6. hosting::add_to_waitlist();
  7. }
mod front_of_house 后使用分号,而不是代码块,这将告诉 Rust 在另一个与模块同名的文件中加载模块的内容。
  1. pub mod hosting {
  2. pub fn add_to_waitlist() {}
  3. }

重构例子

hosting 模块也提取到其自己的文件中
  1. pub mod hosting;
接着我们创建一个 src/front_of_house 目录和一个包含 hosting 模块定义src/front_of_house/hosting.rs 文件
  1. pub fn add_to_waitlist() {}
模块树依然保持相同,eat_at_restaurant 中的函数调用也无需修改继续保持有效,即便其定义存在于不同的文件中。这个技巧让你可以在模块代码增长时,将它们移动到新文件中。 注意,src/lib.rs 中的 pub use crate::front_of_house::hosting 语句是没有改变的,在文件作为 crate 的一部分而编译时,use 不会有任何影响。mod 关键字声明了模块,Rust 会在与模块同名的文件中查找模块的代码。