随着项目规模的增长,组织代码变得越来越重要。本章介绍了Rust的模块系统,它允许开发者将代码拆分为多个文件,管理可见性,并控制作用域。
Crate是Rust编译器一次考虑的最小代码单位,分为两种形式:
main
函数main
函数,不编译为可执行文件crate根是编译器开始编译的源文件,也是crate的根模块:
src/main.rs
src/lib.rs
包(Package)是一个或多个crate的集合,提供一组功能,包含一个Cargo.toml
文件:
创建包的例子:
$ cargo new my-project
$ ls my-project
Cargo.toml src
$ ls my-project/src
main.rs
Cargo约定:
src/main.rs
是与包同名的二进制crate的crate根src/lib.rs
是与包同名的库crate的crate根src/bin
目录放置文件来拥有多个二进制crate模块(module)用于将代码组织成逻辑单元,控制项目的可见性(公有或私有)。
模块速查表:
从crate根开始:编译器首先在crate根文件中查找代码
声明模块
:在crate根文件中,可以使用
mod garden;
声明模块,编译器会在以下位置查找代码:
内联,大括号中的代码:mod garden { ... }
文件:src/garden.rs
文件:src/garden/mod.rs
(旧风格)
声明子模块
:在非crate根文件中,可以声明子模块,编译器会在以下位置查找:
内联,大括号中的代码
文件:src/garden/vegetables.rs
文件:src/garden/vegetables/mod.rs
(旧风格)
模块中代码的路径:一旦模块是crate的一部分,可以使用路径引用其代码
私有与公有:默认情况下,模块中的代码对父模块是私有的,使用pub
关键字使其公有
use关键字:创建简短路径的快捷方式,减少重复的长路径
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() {}
}
}
模块树表现为:
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
路径有两种形式:
crate
关键字开头self
、super
或当前模块中的标识符// 绝对路径
crate::front_of_house::hosting::add_to_waitlist();
// 相对路径
front_of_house::hosting::add_to_waitlist();
默认情况下,Rust中的所有项目对父模块都是私有的。要使项目可见,需要使用pub
关键字:
mod front_of_house {
pub mod hosting { // 公有模块
pub fn add_to_waitlist() {} // 公有函数
}
}
pub fn eat_at_restaurant() {
// 可以访问,因为hosting是公有的,add_to_waitlist也是公有的
front_of_house::hosting::add_to_waitlist();
}
可以使用super
关键字构建以父模块开始的相对路径:
fn deliver_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::deliver_order(); // 使用super访问父模块中的函数
}
fn cook_order() {}
}
结构体:
pub
标记结构体时,结构体是公有的,但字段仍然是私有的pub
使其公有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
标记枚举时,所有变体都是公有的mod back_of_house {
pub enum Appetizer {
Soup, // 自动公有
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 crate::front_of_house::hosting;
hosting::add_to_waitlist();
use std::collections::HashMap;
let mut map = HashMap::new();
当引入同名项目时有几种解决方案:
use std::fmt;
use std::io;
fn f1() -> fmt::Result { ... }
fn f2() -> io::Result<()> { ... }
as
关键字重命名: use std::fmt::Result;
use std::io::Result as IoResult;
fn f1() -> Result { ... }
fn f2() -> IoResult<()> { ... }
使用pub use
可以重新导出名称,使外部代码也能引用这个名称:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
这样,外部代码可以使用restaurant::hosting::add_to_waitlist()
而不是restaurant::front_of_house::hosting::add_to_waitlist()
。
当从同一crate或模块引入多个项目时,可以使用嵌套路径减少代码量:
// 不使用嵌套路径
use std::cmp::Ordering;
use std::io;
// 使用嵌套路径
use std::{cmp::Ordering, io};
如果路径有共同部分:
// 不使用嵌套路径
use std::io;
use std::io::Write;
// 使用嵌套路径
use std::io::{self, Write};
使用*
可以引入路径下的所有公有项目:
use std::collections::*;
通配符应谨慎使用,因为它会使作用域中的名称来源变得不清晰。
随着模块增大,可以将它们移到单独的文件中:
// src/lib.rs
mod front_of_house;
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
// src/front_of_house.rs
pub mod hosting {
pub fn add_to_waitlist() {}
}
也可以进一步拆分子模块:
// src/front_of_house.rs
pub mod hosting;
// src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}
Rust支持两种文件路径样式:
src/front_of_house.rs
src/front_of_house/hosting.rs
src/front_of_house/mod.rs
src/front_of_house/hosting/mod.rs
不推荐混合使用这两种风格,因为可能造成混淆。
Rust的模块系统允许:
use
创建简短的路径名pub use
重新导出名称合理使用这些特性,可以创建组织良好、易于理解和维护的代码库。
好好学习,天天向上