文章

Rust基础——模块化

开始

rust的模块化包含以下概念

  • 包:一个包包含多个crates,每个包都有一个Cargo.toml文件,一个包可以看做一个项目
  • crates:一个crates是一个编译单元,可以是一个可执行文件或一个库
  • 模块:一个模块表示一个命名空间,一个crates中包含多个模块,可以控制模块的可访问性
  • 路径:访问一个模块或模块成员的方式,使用::作为分隔符

包和crate

crate有两种形式

  • 二进制项:二进制项可以被编译为一个可执行文件,必须包含一个main函数
  • 库:库没有main函数,其中定义一些类型或函数,可供其他crate使用

一个包可以看做一个项目,使用cargo new命令创建,其中包含一个Cargo.toml文件,用于管理依赖

一个包中至少包含一个crate,最多包含一个库crate,可以包含任意多个二进制crate

每个crate都至少有一个根模块crate,也称为crate根,cargo有以下默认约定

  • src/main.rs:与包同名的二进制crate根
  • src/lib.rs:与包同名的库crate根
  • src/bin:存放其他的二进制crate

模块

一个模块可以看做一个命名空间

  • 使用mod关键字声明一个子模块
  • 使用pub关键字控制模块的可访问性
  • 使用use关键字引入模块

模块组织形式

一个crate中的模块被组织成一个树形结构,支持内联多文件两种形式

  • 内联:使用mod关键字声明一个模块后,紧跟一个代码块,在代码块中编写模块的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    // 通过crate::module访问module模块
    // 通过crate::module::submodule访问子模块
    mod module {
        struct Tuple(i32, i32);
        fn foo(x: i32, y: i32) {}
          
        mod submodule {
            fn foo(x: i32, y: i32) {}
        }
    }
    
  • 多文件:利用文件树的形式,在父模块中使用mod关键字声明一个子模块,cargo会在子目录中查找子模块

    1
    2
    3
    4
    5
    
    src
    ├──module  // 与module.rs同名
    │  └──submodule.rs
    ├──main.rs
    └──module.rs
    
    • main.rs中使用mod module;声明module子模块,cargo会查找module.rs
    • module.rs中使用mod submodule;声明submodule子模块,cargo会查找module/submodule.rs
    • 使用crate::module访问module模块,使用crate::module::submodule访问submodule模块

内联多文件两种形式可以混合使用

submodule.rs中的模块代码以内联形式移动到module.rs

1
2
3
4
5
6
7
// 依然使用crate::module::submodule访问submodule
mod submodule {
    fn foo(x: i32, y: i32) {}
}

struct Tuple(i32, i32);
fn foo(x: i32, y: i32) {}

模块可访问性

在默认情况下,模块内的成员是私有的,一个模块可以直接访问模块内的成员,子模块可以访问父模块中的成员

1
2
3
4
5
6
7
8
9
10
mod submodule {
    fn foo(x: i32) {
        super::foo(3);  // 使用super关键字引用父模块
    }
}
struct Structure;
fn foo(x: i32) {
    let s = Structure;  // 可直接访问模块内的成员
    submodule::foo(2);  // 不合法,可以直接访问submodule,但无法访问submodule的成员foo
}

使用pub关键字将其变为公有

1
2
3
4
5
pub mod submodule {
    fn foo(x: i32) {}
}
pub struct Structure;
pub fn foo(x: i32) {}

此时,submodule子模块、Structure结构体和foo函数作为模块内的成员,对外部是可见的,但submodule子模块内的成员依然对submodule是私有的,若需要外部访问submodule子模块内的成员,依然使用pub关键字修饰

1
2
3
4
5
pub mod submodule {
    pub fn foo(x: i32) {}
}
pub struct Structure;
pub fn foo(x: i32) {}

pub关键字还可以控制结构体成员和枚举成员的可访问性

  • 结构体成员默认为私有的,可以用pub修饰变为公有

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,  // seasonal_fruit为私有字段
    }
      
    impl Breakfast {
        // 公有关联函数
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
    
  • 枚举成员默认为私有的,一旦枚举类型用pub修饰变为公有,则所有成员变为公有

    1
    2
    3
    4
    
    pub enum Appetizer {
        Soup,
        Salad,
    }
    

引入模块

绝对路径与相对路径

使用use关键字引入一个模块或模块成员,在引入时的路径格式为A::B::C,有两种形式

  • 绝对路径:以根模块crate开始

    1
    
    use crate::module::submodule;
    
  • 相对路径:默认以当前模块开始,self关键字表示当前模块,super关键字表示父模块

    1
    2
    3
    4
    
    // 当前模块为src/module.rs
    use submodule::foo;  // 对应绝对路径为crate::module::submodule::foo
    use self::submodule;  // 对应绝对路径为crate::module::submodule
    use super::other_module;  // 对应绝对路径为crate::other_module
    

use关键字

使用use引入的惯用做法

  • 对于模块中的函数,引入到模块级别

    1
    2
    3
    4
    5
    6
    7
    
    mod front_house {
        pub mod hosting {
            pub fn add_waitlist() {}
        }
    }
      
    use front_house::hosting;  // 调用hosting::add_waitlist();
    
  • 对于模块中的类型,引入到成员级别

    1
    2
    3
    4
    5
    6
    7
    
    mod front_house {
        pub mod hosting {
            pub struct House;
        }
    }
      
    use front_house::hosting::House;  // 直接使用House类型
    

use引入的模块或模块成员只在当前模块中可见

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mod front_house {
    pub mod hosting {
        pub fn add_waitlist() {}
    }
}

use front_house::hosting;

// 只作用在当前模块中
fn foo() {
    hosting::add_waitlist();
}

mod customer {
    pub fn eat() {
        hosting::add_waitlist();  // 不合法,在customer模块中不能直接访问引入的hosting
    }
}

若需要外部访问当前模块中引入的模块或模块成员,可以使用pub use重导出

1
pub use front_of_house::hosting;

当引入发生名称冲突时,使用as关键字为某个名称起别名

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

use引入支持嵌套路径通配符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
mod front_house {
    
    pub mod hosting {
        pub struct House;
        
        pub mod serving {
            pub fn serve() {}
        }
    }
    
    pub fn foo() {}
    
    pub mod eating {
        pub fn eat() {}
    }
}

use front_house::{
    foo,  // 引入foo函数
    eating::*,  // 使用通配符引入eating模块中的所有公有成员,不包含eating本身
    // 嵌套路径
    hosting::{
        self,  // 使用self关键字引入hosting模块本身
        House,  // 引入House结构体
        serving,  // 引入子模块
    }
};

注意,在直接路径中,selfsuper只能用于路径开头,而在嵌套路径中,self多了一个引入某一级模块本身的功能

本文由作者按照 CC BY 4.0 进行授权