Rust 学习-12-包Crate模块

Rust 有许多功能可以让你管理代码的组织,包括哪些内容可以被公开,哪些内容作为私有部分,以及程序每个作用域中的名字。这些功能,有时被统称为 “模块系统(the module system)”,包括:

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

包和Crates

crate 是 Rust 在编译时最小的代码单位。如果你用 rustc 而不是 cargo 来编译一个文件(第一章我们这么做过),编译器还是会将那个文件认作一个 crate。crate 可以包含模块,模块可以定义在其他文件,然后和 crate 一起编译。

crate 有两种形式:二进制项和库。 二进制项 可以被编译为可执行程序,比如一个命令行程序或者一个服务器。

并没有 main 函数,它们也不会编译为可执行程序,它们提供一些诸如函数之类的东西,使其他项目也能使用这些东西。

package )是提供一系列功能的一个或者多个 crate。一个包会包含一个 Cargo.toml 文件,阐述如何去构建这些 crate。Cargo 就是一个包含构建你代码的二进制项的包。Cargo 也包含这些二进制项所依赖的库。其他项目也能用 Cargo 库来实现与 Cargo 命令行程序一样的逻辑。

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

模块

  • 从 crate 根节点开始 : 当编译一个 crate, 编译器首先在 crate 根文件(通常,对于一个库 crate 而言是 src/lib.rs ,对于一个二进制 crate 而言是 src/main.rs )中寻找需要被编译的代码。
  • 声明模块: 在 crate 根文件中,你可以声明一个新模块;比如,你用 mod garden 声明了一个叫做 garden
  • 的模块。编译器会在下列路径中寻找模块代码:
    • 内联,在大括号中,当 mod garden 后方不是一个分号而是一个大括号
    • 在文件 src/garden.rs
    • 在文件 src/garden/mod.rs
  • 声明子模块: 在除了 crate 根节点以外的其他文件中,你可以定义子模块。比如,你可能在src/garden.rs中定义了 mod vegetables; 。编译器会在以父模块命名的目录中寻找子模块代码:
    • 内联,在大括号中,当 mod vegetables 后方不是一个分号而是一个大括号
    • 在文件 src/garden/vegetables.rs
    • 在文件 src/garden/vegetables/mod.rs
  • 模块中的代码路径 : 一旦一个模块是你 crate 的一部分,你可以在隐私规则允许的前提下,从同一个 crate 内的任意地方,通过代码路径引用该模块的代码。举例而言,一个 garden vegetables 模块下的 Asparagus 类型可以在 crate::garden::vegetables::Asparagus 被找到。
  • 私有 vs 公用 : 一个模块里的代码默认对其父模块私有。为了使一个模块公用,应当在声明时使用 pub mod 替代 mod 。为了使一个公用模块内部的成员公用,应当在声明前使用 pub
  • use 关键字 : 在一个作用域内, use 关键字创建了一个成员的快捷方式,用来减少长路径的重复。在任何可以引用 crate::garden::vegetables::Asparagus 的作用域,你可以通过 use crate::garden::vegetables::Asparagus; 创建一个快捷方式,然后你就可以在作用域中只写 Asparagus 来使用该类型。

声明模块

使用 mod xxx 的方式来声明模块。

 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() {}
     }
 }
 

分别声明了front_of_house模块以及其子模块。

如果上面的模块采用Tree形式, 将如下结构

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

引用模块

引用模块即如何再模块树中找到对应的位置,Rust中使用路径的方式。

路径方式的两种形式:

  • 绝对路径 absolute path )是以 crate 根(root)开头的全路径;对于外部 crate 的代码,是以 crate 名开头的绝对路径,对于当前 crate 的代码,则以字面值 crate 开头。
  • 相对路径 relative path )从当前模块开始,以 self super 或当前模块的标识符开头。
 
 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();
 
     // 相对路径
     front_of_house::hosting::add_to_waitlist();
 }
 

绝对路径, 即从根开始依次找到对应的位置。

相对路径即相对当前模块依次找到对应的位置。

其中, 相对路径可以使用self\super的方式来定位, 类似于 . .. ,即当前模组或上层模组

共有、私有

当一个模组中要被其他模组方式,则要使用 pub 的形式共开。

例如上面的代码, eat_at_restaurant的函数中, 要访问add_to_waitlist的函数则需要pub hosting模块以及pub add_to_waitlist的函数。

注意:Struct 结构体,如果在模块中, 其字段需要公开后访问。

enum ,公开后则其所有的成员都是公开的。

 mod back_of_house {
     pub struct Breakfast {
         pub toast: String,
         seasonal_fruit: String,
     }
 
     impl Breakfast {
         pub fn summer(toast: &str) -> Breakfast {
             Breakfast {
                 toast: String::from(toast),
                 seasonal_fruit: String::from("peaches"),
             }
         }
     }
 }
 
 pub fn eat_at_restaurant() {
     // 在夏天订购一个黑麦土司作为早餐
     let mut meal = back_of_house::Breakfast::summer("Rye");
     // 改变主意更换想要面包的类型
     meal.toast = String::from("Wheat");
     println!("I'd like {} toast please", meal.toast);
 
     // 如果取消下一行的注释代码不能编译;
     // 不允许查看或修改早餐附带的季节水果
     // meal.seasonal_fruit = String::from("blueberries");
 }
 
 
 mod back_of_house {
     pub enum Appetizer {
         Soup,
         Salad,
     }
 }
 
 pub fn eat_at_restaurant() {
     let order1 = back_of_house::Appetizer::Soup;
     let order2 = back_of_house::Appetizer::Salad;
 }
 

引用模块

当写代码是, 每次使用路径的方式调用函数等不便编写, 可以使用 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();
 }
 

引用说明:

  • 可以将常用的方法,使用use的方式引入到作用域。
 mod front_of_house {
     pub mod hosting {
         pub fn add_to_waitlist() {}
     }
 }
 
 use crate::front_of_house::hosting::add_to_waitlist;
 
 pub fn eat_at_restaurant() {
     add_to_waitlist();
 }
 
  • 可以使用as关键字提供新的名称
 use std::fmt::Result;
 use std::io::Result as IoResult;
 
 fn function1() -> Result {
     // --snip--
     Ok(())
 }
 
 fn function2() -> IoResult<()> {
     // --snip--
     Ok(())
 }
 
  • 使用pub use重导出名称, 重导出 技术将一个路径导出,允许别人把它导入他们自己的作用域中。
  • Use 可以使用嵌套方式消除大量的use行
 // --snip--
 use std::cmp::Ordering;
 use std::io;
 // --snip--
 
 
 // 变为如下
 use std::{cmp::Ordering, io};
 
use std::io;
use std::io::Write;

//->
use std::io::{self, Write};

  • Use 可以通过glob运算符引入所有的公有定义引入到作用域

use std::collections::*;

使用外部包

  1. Cargo.toml 中加入引入的包
rand = "0.8.5"
  1. use 引用的包
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}");
}