本文是读《Rust程序设计语言第二版》Mod和文件系统相关内容的笔记。阅读这本书所敲的代码放在Github上。代码没有按书的结构分章节创建工程,而是将所有代码放在一个单独的工程中。

模块

使用Cargo创建新项目时,默认创建的是二进制crate而不是创建库crate。创建库crate要使用--lib参数而不是--bin参数:

1
2
$ cargo new communicator --lib
$ cd communicator

这时会生成src/lib.rs而不是src/main.rs

在上面链接的示例中,我没有使用这种方式创建mod。因为敲的所有示例都在一个工程中,所以只需要添加lib.rsCargo.toml中也没有增加内容,只是添加了调用lib.rsuselib.rs

模块定义

Rust默认只知道lib.rs中的内容,通过它来查找对应的模块名.rs

可以在src/lib.rs中定义一个或多个模块

1
2
3
4
5
6
7
8
9
mod network {
    fn connect() {
    }
}

mod client {
    fn connect() {
    }
}

在模块外调用这些函数,需要指定模块名并使用命名空间语法::,如:network::connect()

模块间是可以嵌套的:

1
2
3
4
5
6
7
8
9
mod network {
    fn connect() {
    }

    mod client {
        fn connect() {
        }
    }
}

模块移动到其它文件

位于层级中的模块,非常类似于文件系统结构。可以利用Rust模块系统,使用多个文件分解Rust项目。这样就不需要将所有代码都放在src/lib.rssrc/main.rs了。

下面我们将要把下面的clientnetworkserver三个模块拆分至各自的.rs文件中。

src/lib.rs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
mod client {
    fn connect() {
    }
}

mod network {
    fn connect() {
    }

    mod server {
        fn connect() {
        }
    }
}

第一步:拆分client

src/lib.rs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
mod client;

mod network {
    fn connect() {
    }

    mod server {
        fn connect() {
        }
    }
}

src/client.rs

1
2
fn connect() {
}

注意在上面的client.rs里,不再需要mod声明,因为在src/lib.rs中已经声明了client mod

第二步:拆分network

src/lib.rs

1
2
3
mod client;

mod network;

src/network.rs

1
2
3
4
5
6
7
fn connect() {
}

mod server {
    fn connect() {
    }
}

这个拆分方法与上次一样,只不过在network.rs中,保留了server模块的声明。

第三步:拆分server

如果我们按上面的方式继续拆。就是将src/network.rs改为

1
2
3
4
fn connect() {
}

mod server;

并增加src/server.rs

1
2
fn connect() {
}

但是,在这样修改后cargo build会报错。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$ cargo build
   Compiling communicator v0.1.0 (file:///projects/communicator)
error: cannot declare a new module at this location
 --> src/network.rs:4:5
  |
4 | mod server;
  |     ^^^^^^
  |
note: maybe move this module `src/network.rs` to its own directory via `src/network/mod.rs`
 --> src/network.rs:4:5
  |
4 | mod server;
  |     ^^^^^^
note: ... or maybe `use` the module `server` instead of possibly redeclaring it
 --> src/network.rs:4:5
  |
4 | mod server;
  |     ^^^^^^

这说明src/network.rssrc/lib.rs在某些方面是不同的。错误信息中建议的方式是:

  1. 新建名为network的目录,这是父模块的名字。

  2. src/network.rs移至新建的network目录,并重命名为src/network/mod.rs

  3. src/server.rs移动到network目录中。

整个目录结构变为:

1
2
3
4
5
6
└── src
    ├── client.rs
    ├── lib.rs
    └── network
        ├── mod.rs
        └── server.rs

移动完毕后,各文件的内容如下:

  1. src/lib.rs
1
2
3
pub mod client;

pub mod network;
  1. src/client.rs
1
2
3
pub fn connect(){
    println!("client::connect");
}
  1. src/network/mod.rs
1
2
3
4
5
pub fn connect(){
    println!("network::connect()");
}

pub mod server;
  1. src/network/server.rs
1
2
3
4
5
pub  fn connect(){
    println!("network::server::connect()");
    println!("in server mod,super::connect() = network::connect() : ");
    super::connect();
}

使用src/usrlib.rs调用这些模块:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
mod client;
mod network;

use client::connect;

//use network::connect;

fn main() {
    connect();
    network::connect();
    network::server::connect();
    // 从根模块开始引用
    ::client::connect();
}

模块文件系统的规则

  • 如果foo模块没有子模块,应该foo的声明放在foo.rs文件中。

  • 如果foo模块有子模块,应该将foo的声明放在foo/mod.rs中。

使用pub控制可见性

使用extern crate communicator可以从外部模块中将communicatorcrate引入到作用域。从外部crate的角度来看,我们所创建的所有模块都位于一个与crate同名的模块内,即位于communicator内部。这个顶层模块被称为crate根模块

即便在项目的子模块中使用外部crateextern crate也应该位于根模块(即src/main.rssrc/lib.rs中)。在子模块中,我们可以像顶层模块那样引用外部crate中的项了。

Rust上下文中涉及公有私有的概念。所有代码默认是私有的,除了自己之外,别人不允许使用这些代码。如果不在自己的项目中使用某个函数,编译器会警告该函数未被使用。

为了将函数标记为公有,需要在声明的开头增加pub关键字。

私有性规则

  • 如果一个项是公有的,它能被任何父模块访问

  • 如果一个项是私有的,它能被其直接父模块及任何子模块访问

在不同模块中引用命名

使用use关键字将指定的模块引入作用域;它并不会将其子模块也引入。

枚举也像模块一样组成了某种命名空间,也可以使用use来导入枚举成员。

可以使用*语法,也称glob运算符将某个命名空间下的所有名称都引入作用域:use TrafficLight::*;

使用super关键字访问父模块:super::client::connect();