Skip to main content
4 包、crates、模块

4 包、crates、模块

•2 min•231 words
前言

之前学Dart的时候已经理解了包、库的概念。Dart概要
可是Rust明显拥有更复杂和完备的概念。

最小单位crate

Rust编译器的包概念

Dart中的包,就和这里的Crate是一个意思,代表了整个程序。与包有直接关系是模块,而模块是定义在文件中的。

crate分两“二进制”crate,和“库crate“。

二进制 crate(Binary crates)可以被编译为可执行程序,比如命令行程序或者服务端。通过cargo new <项目名> --bin来创建。
库 crate(Library crates)并没有 main 函数,它们也不会编译为可执行程序。相反它们定义了可供多个项目复用的功能模块。通过cargo new <项目名> --lib来创建。

Cite
crate的root:根

Rust 编译器以root为起始点,并构成你的 crate 的根模块。
通常我们通过编译器指定,但是如果我们使用自动构建工具Cargo,需要知道cargo的默认crate root。

Cargo 遵循的一个约定:src/main.rs 就是一个与包同名的二进制 crate 的 crate 根。
包目录中包含 src/lib.rs,则包带有与其同名的库 crate,且 src/lib.rs 是 crate 根。

同名库crate与同名二进制crate

虽然库文件与二进制文件同名,但是在二进制中使用crate代表自己,所以这个名字就可以指代同名库文件了。而且二进制文件不向外提供接口,所以该名字只能指代库文件。

配置区分二进制与库项目

可以通过在Cargo.toml中配置[lib]属性,可以给lib设置别的名称

[lib]
# The `_lib` suffix may seem redundant but it is necessary
# to make the lib name unique and wouldn't conflict with the bin name.
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
name = "passwd_nickname_lib"
crate-type = ["staticlib", "cdylib", "rlib"]

当一个 Cargo 项目同时有 [lib] 和 [[bin]] 时:

  • 默认情况下,库名和二进制名都取自 Cargo.toml 中的 package.name(比如叫 my-api)。

  • 如果恰好有个 src/main.rs(默认 binary)和一个 src/lib.rs(默认 library),它们都会产出同名的产物:库文件 libmy_api.rlib 和可执行文件 my-api(Windows 上是 my-api.exe)。这本身不冲突,因为产物类型不同。

  • 但有时我们希望明确区分,避免在代码中 use my_api; 时可能产生的歧义(比如某些工具链或特殊配置下),或者为了更清晰地表达“这个库是专门用来生成 API 文档/规范的”。

你看到的注释建议给库名加上 _lib 后缀,变成 my_api_lib,这样:

  • 库名唯一:my_api_lib

  • 二进制名保持简洁:my-api

  • 在依赖或文档生成时,名字不会撞车

Package项目

Cargo管理工具的单位

如果想要一个crate被cargo管理,那么必须要成为一个package。
通常package的条件是cargo.toml文件。

Cite

包中可以包含至多一个库 crate(library crate)。包中可以包含任意多个二进制 crate(binary crate),但是必须至少包含一个 crate(无论是库的还是二进制的)。

导入别的crate

rustc 外部库机制

rustc 接收--extern 名字=路径 参数 → 把这个名字注册为全局 Crate → 代码里 use 名字::xxx 就能找到

cargo的工作

在cargo.toml中配置了外部依赖之后,cargo会下载到本地缓存。然后将第三方Crate 编译成Rust 库文件(.rlib),编译后的产物,统一放在编译目标目录的依赖目录:/target/debug/deps/
然后Cargo 会执行 rustc 命令,并带上 --extern 参数,把 Crate 路径直接告诉编译器

Summary

内置crate

rustc内置crate可以直接在代码中使用

core/std/alloc这几个crate是编译器内置的crate,可以在rust代码中直接访问使用。

Module模块

crate的组成部分

模块(Module):一个文件可以表示多个模块,也可以一个文件只表示一个模块,模块可以被认为是真实项目中的代码组织单元

模块声明

先定声明才能被找到

我们需要将模块的声明通知写在crate root中,这样才能让编译器找到我们的模块代码所在位置。

pub mod 模块1;
 fn main(){
	 // 知道有模块1的代码
 }

模块可能的位置

让编译器找到模块代码

为了灵活性(毕竟模块不就是为了灵活吗),模块代码的位置可以在当前文件,专属文件,专属目录中。

python中似乎也是这样:

  1. 可以在内联中
mod linner_module_code {
	pub let s = String::from("hello world!");
}
  1. 可以在专属文件中
mod file_module_code;
// 然后编译器发现没有代码块,尝试寻找src/file_module_code.rs中
  1. 可以在专属目录中
mod dir_module_code;
// 然后编译器发现没有代码块,尝试寻找src/dir_module_code.rs中
// 没找到
// 继续尝试找src/dir_module_code/mod.rs
不能混用模块默认位置

使用“专属文件”的模块位置风格是较新的,而较旧的风格“mod.rs”虽然仍然支持,但是如果你对同一个模块同时使用这两种风格,编译器就会报错。在同一个项目里为不同模块混用两种风格是允许的,不过这可能会让浏览项目的人感到困惑。

模块中的模块

模块中的模块可能的位置

首先就是内联代码块,然后就从父模块的专属目录开始,找模块的专属文件,以及专属目录。

声明子模块: 在除了 crate 根节点以外的任何文件中,你可以定义子模块。比如,你可能在 src/garden.rs 中声明 mod vegetables;。编译器会在以父模块命名的目录中寻找子模块代码:
- 内联,直接在 mod vegetables 后方不是一个分号而是一个大括号
- 在文件 src/garden/vegetables.rs
- 在文件 src/garden/vegetables/mod.rs

访问修饰

要对外可见需要标注pub

私有 vs 公用: 一个模块里的代码默认对其父模块私有。为了使一个模块公用,应当在声明时使用 pub mod 替代 mod。为了使一个公用模块内部的成员公用,应当在声明前使用pub。

内部代码块都对外部模块不可见需要全部加pub

比如pub mod中的结构体中的字段也是对外private的需要额外加pub修饰字段。

通过访问修饰来实现强制只能使用new创建结构体

我们对pub struct中的字段不进行pub,这样外部模块无法访问内部字段,而这些字段没有空可能,所以无法通过外部直接构建,只能使用pub的new方法构建。

访问模块代码

我们需要唯一表示模块的路径

有两种方式,一种绝对路径表示,以crate隐式模块开头的::路径。
一种相对路径表示方式,当前模块作为起点,或者当前父模块为起点,只用写相对路径。

crate起头链接路径

[!summary] crate起头::链接路径
访问农田模块中的蔬菜模块中的萝卜类,可以这样表示:crate::农田::蔬菜::萝卜
但是得满足访问限制条件。

和关键字在路径中

[!summary] self和super关键字在路径中
使用相对路径的时候,可以使用self,super关键字来表示当前目录,和父目录路径。
当然,self可以默认省略。

use关键字

use一个类,避免写全路径

如果我们声明use了一个类型,我们就可以直接写类名或者代码名之类的了。

路径重用

可以用大括号表示分支路径,而大括号前的就是共用路径,以此来避免太多的重复

Summary
use std::collections::*;
不像import,而更像普通代码!

use语句可以写在文件的任意位置而非文件首行!这对与条件编译非常有用,我们可以将use写在条件编译代码块内部。

pub use重导出

传递本模块可见的代码

因为默认向上隐瞒,我们可以使用pub use 向上公开下边我们知道的代码。而不用通过 pub mod链。通常用于统一外部接口过长的内部模块链。

as 关键字

重命名

也许还记得as在类型强转的作用,as也可以用在use声明中,将一个类名或者什么方法之类的名字取一个别名。

use std::io::Result as IoResult;