5分钟速读之Rust权威指南(十一)

205 阅读5分钟

Module

在编写较为复杂的项目时,合理地对代码进行组织与管理很重要,rust提供了一系列的功能来帮助我们管理代码,包括决定哪些细节是暴露的、哪些细节是私有的,以及不同的作用域内存在哪些名称。

通过定义模块来控制作用域及私有性

在src目录下创建lib.rs文件,使用mod关键字加大括号定义一个模块,可以多层嵌套:

// src/lib.rs
mod front_of_house {
    // 模块中的内容默认都是隐藏的,需要使用pub将其公开
    pub mod hosting {
	pub fn add_to_waiting() {}
	fn seat_to_table() {}
    }
    mod serving {
	fn take_order() {}
        fn serve_order() {
            fn take_payment() {}
    }
}

fn eat_at_restaurant() {
    // 引用模块内容
    // 绝对路径使用crate开头,因为front_of_house是根模块,
    // 可以理解为绝对路径: /front_of_house
    crate::front_of_house::hosting::add_to_wait_list();

    // 使用相对路径,因为front_of_house和
    // eat_at_restaurant在同一级,可以理解为:./front_of_house
    front_of_house::hosting::add_to_wait_list()
}

super关键字

每一个模块是单独的作用域,而且作用域并不会自动向上查找,查找当前模块的上级作用域,可以使用super:

// back_of_house模块的作用域外部
fn serve_order() {}

mod back_of_house {
  fn cook_order() {}

  fn fix_incorrent_order() {
	// 当前模块作用域中可以直接使用
	cook_order();

	// 获取父级模块的内容,可是使用super关键字开头,可以理解为:../
	super::serve_order()
    }
}

公开的结构体

当我们在结构体定义前使用pub时,结构体本身就成为了公共结构体,但它的字段依旧保持了私有状态。我们可以逐一决定是否将某个字段公开:

mod back_of_house {
	pub struct Breakfast {
            // 只公开toast字段
            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");

        // 可以访问更改toast属性
	meal.toast = String::from("Wheat");
	println!("I'd like {} toast please", meal.toast);
        // I'd like Wheat toast please

	// 无法编译通过,因为seasonal_fruit不是公开属性
	meal.seasonal_fruit = String::from("blueberries");
}
eat_at_restaurant();

公开的枚举

当我们将一个枚举声明为公共的时,它所有的变体都自动变为了公共状态。我们仅需要在enum关键字前放置pub:

mod back_of_house {
	// 对枚举添加pub,会将所有的枚举值公开
	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关键字引用模块

use可以引用当前模块中的内容,支持绝对路径和相对路径:

mod front_of_house {
    pub mod hosting {
	pub fn add_to_wait_list() {}
    }
}

// 绝对路径方式
use crate::front_of_house::hosting; // 方式1
use crate::front_of_house::hosting::add_to_wait_list; // 方式2

// 或者相对路径
use self::front_of_house2::hosting; // 方式1
use self::front_of_house2::hosting::add_to_wait_list; // 方式2

fn eat_at_restaurant() {
    hosting::add_to_wait_list() // 方式1使用
    add_to_wait_list() // 方式2使用
}

使用as关键字对引用模块重命名

当我们引用相同名称的内容会引起冲突,这是需要使用as关键字对其中一项进行重命名:

use std::fmt::Result;
use std::io::Result as IoResult;

重新导出(reexporting)

我们对用户暴露的内容通常需要暴露在一个出口中,这样方便用户导入,

我们需要先将其他的引用导入到同一个模块中,然后重新导出:

pub use crate::front_of_house2::hosting;
pub use std::fmt::Result;

外部模块

当我们使用第三方模块时,需要先在cargo.toml中添加依赖,例如使用一个能够获取随机数的包:

[dependencies]
rand = "0.5.5"
// 运行 cargo build

引入rand:

use rand::Rng;
fn main() {
  let secret_number = rand::thread_rng().gen_range(1, 101);
}

引入标准库中的模块,标准库虽然不用在toml中声明,也不用下载,但是也需要显式引入:

use std::collections::HashMap;

嵌套批量导入

use std::{cmp::Ordering, io};
// 等同于
use std::cmp::Ordering;
use std::io;

使用self表示引用导入路径的自身:

use std::io::{self, Write};
// 僧同于
use std::io;
use std::io::Write;

使用*运算符导入所有

// 引入collections下面所有的内容
use std::collections::*
// 直接使用引入的内容
BTreeMap::new();

将模块拆分到不同的文件

上边的所有例子都是在同一个文件中,我们可以把不同功能的代码拆分到不同的文件中维护,有两种方式可以实现,例如我们要在lib.rs中使用如下结构的模块:

// src/lib.rs
mod a; // 引入a模块
use a::structs::{ A1, A2 }; // 使用a模块中的结构体中的A1,A2结构体

方式一

使用模块同名文件夹结构来组织,这种组织方式将一个模块的同名文件夹作为模块的内容,模块本身作为入口文件,首先创建a.rs文件作为a模块的入口文件:

touch src/a.rs

创建a模块的同名文件夹,用于放置a模块的内容:

mkdir src/a

在文件夹a中创建结构体模块:

touch src/a/structs.rs

创建A1和A2结构体:

// src/a/structs.rs
pub struct A1 {}
pub struct A2 {}

最后在a模块的入口文件中引入structs模块并向外导出:

// src/a.rs
pub mod structs;

最后结构如下:

src
├── a
│   └── structs.rs
├── a.rs
├── lib.rs

方式二

使用模块入口放在模块文件夹中的方式来组织,这种方式将文件夹作为模块名称,在文件夹中使用mod.rs作为模块入口,首先创建模块文件夹:

mkdir src/a

在文件夹a中创建结构体模块:

touch src/a/structs.rs

创建A1和A2结构体:

// src/a/structs.rs
pub struct A1 {}
pub struct A2 {}

本次不再创建src/a.rs文件,而是在文件夹a中放置入口文件mod,名称是固定的,就好像我们在JS中一般使用index.js如出一辙:

touch src/a/mod.rs

在入口文件中引入并导出a模块的内容:

// src/a/mod.rs
pub mod structs;

这种方式的目录接否如下:

src
├── a
│   ├── mod.rs
│   └── structs.rs
├── lib.rs

本章节只介绍了模块的基本用法和两种目录结构的使用方式,更详细的内容可以自行去翻阅原书。