7.1 Package, Crate, Module

概览

Package

  • Cargo的特性,用于构建、共享和测试crate

Crate

  • 一个模块树,可以产生一个library或可执行文件

Module

  • use,让我们控制代码的组织、作用域和私有路径

Path

  • 为struct,module,function等命名的方式

Package和crate

Crate分为binary和library两种

  • 源代码文件被称作crate root,组成crate的root module,也是编译器开始的地方

在一个package里面

  • 只能包含一个Cargo.toml,其描述了如何构建这些crate
  • 只能包含0-1个library crate,但binary crate不限
    • 不过必须至少包含1个crate

上面的有些抽象,以下是一个具体的粒子


在执行cargo new my_project后,cargo会创建一个package(项目)

  • src/main.rs
    • 对应的的就是binary crate的crate root
    • crate的名与package的名相同

如果我们额外有

  • src/lib.rs
    • 则标明该package包含一个library crate
    • 这也是library crate的crate root
    • 这个crate名也与package名相同

Cargo会把crate root里的文件交给rustc来构建对应的library或者binary文件

对于更多的binary crate,需要放在src/bin文件夹下面,每个源文件是一个单独的binary crate


Crate的作用是将相关功能组合到一个作用域内,便于在项目之间进行共享(防止冲突)

  • 有点像C++里面header + namespace的做法?

例如,之前使用过的rand crate,在访问时需要使用rand


Module

在一个crate里面,我们可以将代码进行分组,即不同的module

Module可以控制内部条目的私有性,例如public和private

为了建立一个module,

  • 使用mod关键字
  • 可嵌套
  • 可包含其它项(如struct,enum,常量,trait,函数等)的定义

例如,在lib.rs中:

1
2
3
4
5
6
7
8
9
10
11
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}

mod serving {
fn take_order() {}
fn serve_order() {}
}
}

7.2-3 Path

为了调用这些定义的模块,我们需要借助它们的path,例如

1
2
3
4
5
6
7
8
9
10
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}

pub fn eat_at_restaurant() {
crate::front_of_house::hosting::add_to_waitlist(); // absolute path
front_of_house::hosting::add_to_waitlist(); // relative path
}
  • 绝对路径从crate的根目录出发,以crate作为出发点,来寻找对应模块里的功能
  • 相对路径从调用者所在的目录出发,来寻找功能
    • 一般建议使用绝对路径,防止代码块移动造成问题

事实上,上面的代码是有误的,因为

  • 模块front_of_house是私有的(对外部调用)

  • 此外hosting模块也是私有的

私有边界(private boundary)

Rust的模块中的所有条目都默认是私有的(struct,enum,常量,trait,函数,子模块等)

声明公共的关键字是pub,因此需要改成

1
2
3
4
5
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
  • 虽然父模块无法访问子模块的私有条目,但是子模块可以访问所有祖先模块的条目

  • 同级条目可以互相调用

Super关键字

为了访问父模块(上级path),使用super关键字,类似bash中的..表示上级目录

例如

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

fn cook_order() {}
}

fn serve_order() {}
  • 其中super::serve_order();也可以替换成绝对路径crate::serve_order();

类似的,模块中定义的struct的条目也是默认私有,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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"); // error! "seasonal_fruit" is private!
}

与struct不同,如果pub声明在枚举类型enum前面,则enum的所有变体都是公共的!

例如

1
2
3
4
5
6
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
  • 和struct不同,enum里的变体如果是私有的,那么就没有意义了
  • 因此,enum是特例

7.4 use关键字

我们可以使用use关键字将路径导入到当前作用域内

  • 仍然遵守私有性原则

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
fn some_function() {}
}
}

use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::some_function(); // error!
}
  • 由于私有性原则依然存在,所以调用私有函数some_function()会报错

除了绝对路径,use还可以指定相对路径,例如上面的例子里,就是

1
use front_of_house::hosting;

事实上,如果调用add_to_waitlist(),我们可以直接

1
use crate::front_of_house::hosting::add_to_waitlist();

但这样在使用函数的时候,难以区分到底是引入的还是本地定义的,因此建议引用到至少一层上级目录


同名条目的as关键字

有可能在两个不同的模块中引入同名的条目,这个时候除了使用上级目录区分,还可以使用as关键字,类似Python中的用法,例如

1
2
3
4
5
use std::fmt::Result;
use std::io::Result as IoResult;

fn f1() -> Result {}
fn f2() -> IoResult {}

7.5 use关键字 续

使用use关键字将路径或名称导入到作用域之后,该名称在此作用域也是默认私有的

例如

1
2
3
4
5
6
7
8
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
fn some_function() {}
}
}

use crate::front_of_house::hosting;
  • 虽然上面的作用域引入了hosting,但外部代码是不能使用use获得的部分的

如果希望外部作用域也能使用到,可以借助

1
pub use crate::front_of_house::hosting;

使用外部package

在Cargo.toml添加依赖的package

使用use将特定条目引入作用域,例如

1
2
3
use rand::Rng;

fn main() {}

在Rust中,标准库std是默认引入的,即不需要在Cargo.toml引入,例如,我们可以直接调用

1
use std::collections::HashMap;

嵌套引入

对于下面的情况

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

可以简写成

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

对于下面的情况

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

我们可以简写成

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

通配符

通配符*可以把路径中的所有公共条目都导入到作用域,例如

1
use::std::collections::*;
  • 谨慎使用通配符!尽量不要使用

7.6 拆分模块到不同文件

例如在lib.rs文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
fn some_function() {}
}
}

use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}

可以修改为

1
2
3
4
5
6
7
8
mod front_of_house;

use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}

随后可以在另一个例如front_of_house.rs文件中定义

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

这并不会影响lib.rs中的use等定义,因为Cargo会自动去寻找相关的模块并绑定到mod front_of_house的位置


如果进一步要将hosting这个子模块单独拿出来定义,需要将源文件放在src/front_of_house/文件夹下才可以

  • 与代码组织相同的路径方式