Rust构建
- 1: Rustc
- 1.1: Rustc的命令行参数
- 1.2: Rustc的条件编译
- 2: Rust Cargo
- 2.1: Cargo概述
- 2.2: Cargo设置
- 2.3: Rust Cargo Book笔记
- 2.3.1: Rust Cargo Book之指南
- 2.3.2: Cargo Reference笔记: 依赖
- 2.3.3: Cargo Reference笔记: 清单
- 2.3.4: Cargo Reference笔记: 工作区
- 2.3.5: Cargo Reference笔记: 特性
- 3: Rust Module
- 3.1: Learn Rust中的Module
- 3.2: Rust编程语言:使用package,crate和module来管理成长中的项目
- 3.2.1: 模块系统
- 3.2.2: Package和Crate
- 3.2.3: 定义模块以控制范围和隐私
- 3.2.4: 用于引用模块树中项目的路径
- 3.2.5: 用use关键字将路径纳入范围
- 3.2.6: 将模块分离到不同的文件中
- 3.3: 对Rust的模块系统的清晰解释
- 4: Rust Lints
- 4.1: Rust Lints概述
- 4.2: Lints等级
- 4.3: Lints分组
- 4.4: 默认为Allow的Lints
- 4.5: 默认为Warn的Lints
- 4.6: 默认为Deny的Lints
- 5: Rustup
- 5.1: Rustup概述
- 5.2: Rustup Book学习笔记
1 - Rustc
介绍
rustc
是 Rust 编程语言的编译器,由项目组开发提供。编译器将您的源代码和生产二进制代码,变成一个或可执行文件。
大多数 Rust 程序员都不会直接调用rustc
,而是通过Cargo来完成,虽然Cargo也是调用rustc
流程!如果想看看 Cargo 如何调用rustc
, 可以
$ cargo build --verbose
它会打印出每个rustc
调用。
资料
- The rustc book: 官方英文版
- The rustc book: 中文翻译版。备注:经常打不开,需要科学上网。另外lint相关的内容和英文文档的内容有很大的偏差。
1.1 - Rustc的命令行参数
英文原文地址: https://doc.rust-lang.org/rustc/command-line-arguments.html
这是 rustc
的命令行参数列表和他们的功能介绍:
-h
/--help
: 帮助
该标志将打印出rustc
的帮助信息。
--cfg
:配置编译环境
此标志可以打开或关闭各种#[cfg]
设置。
该值可以是单个标识符,也可以是由=
分隔两个标识符。
举例,--cfg 'verbose'
或者 --cfg 'feature="serde"'
。分别对应#[cfg(verbose)]
和#[cfg(feature = "serde")]
。
-L
:将目录添加到库搜索路径
查找外部crate或库时,将搜索传递到此标志的目录。
搜索路径的类型可以通过 -L KIND=PATH
方式制定,这是可选的,其中KIND可以是以下之一:
dependency
—仅在此目录中搜索传递依赖项。crate
—仅在此目录中搜索此crate的直接依赖项。native
—仅在此目录中搜索原生类库。framework
—仅在此目录中搜索macOS框架。all
—在此目录中搜索所有库类型。这是KIND未指定时的默认值。
-l
:将生成的包链接到一个原生库
使用此标志可以在构建crate时指定链接到特定的原生库。
可以使用以下形式之一指定库的类型,-l KIND=lib 其中KIND可以是:
类库的类型可以通过 -l KIND=lib
方式制定,这是可选的,其中KIND可以是以下之一:
dylib
— 原生动态库。static
— 原生静态库(例如.a 包)。framework
— macOS框架。
可以在 #[link]
属性中指定库的类型。如果未在 link 属性或命令行中指定种类,它将链接动态库(如果可用),否则将使用静态库。如果在命令行上指定了种类,它将覆盖link属性中指定的种类。
在link属性中使用的 name 可以用 “-l ATTR_NAME:LINK_NAME” 的形式来覆盖,其中 ATTR_NAME
是在 link属性的 name ,而 LINK_NAME
是将被链接的实际库的名称。
--crate-type
:编译器生成包的类型列表
指示rustc要构建的crate类型。该标志接受逗号分隔的值列表,并且可以多次指定。有效的crate类型为:
lib
—生成编译器首选的库类型,当前默认为rlib
。rlib
— Rust静态库。staticlib
—原生静态库。dylib
— Rust动态库。cdylib
—原生动态库。bin
—可运行的可执行程序。proc-macro
—生成适合于程序宏类库的格式,该格式可由编译器加载。
crate类型可以使用 crate_type
属性指定。该 --crate-type
命令行值将覆盖 crate_type
属性。
可以在参考文档的 linkage 章节 中找到更多详细信息。
--crate-name
:指定构建的crate的名称
告诉rustc
crate的名称。
--edition
:指定要使用的版本
此标志的值为 2015 或 2018。默认值为2015。有关版本的更多信息,请参见 版本指南。
--emit
:指定要生成的输出文件的类型
该标志控制编译器生成的输出文件的类型。接受逗号分隔的值列表,并且可以多次指定。有效的生成种类有:
asm
—生成带有crate的汇编代码的文件。默认输出文件名是CRATE_NAME.s
。dep-info
—生成具有Makefile语法的文件,该文件指示已经加载用来生成crate的所有源文件。默认输出文件名是CRATE_NAME.d
。link
—生成由--crate-type
指定的crate。默认输出文件名取决于crate类型和平台。这是--emit
未指定时的默认值。llvm-bc
—生成包含LLVM 字节码的二进制文件。默认输出文件名是CRATE_NAME.bc
。llvm-ir
—生成包含LLVM IR的文件。默认输出文件名是CRATE_NAME.ll
。metadata
—生成包含有关crate元数据的文件。默认输出文件名是CRATE_NAME.rmeta
。mir
—生成包含rustc的中等中间表示形式(mid-level intermediate representation)的文件。默认输出文件名是CRATE_NAME.mir
。obj
—生成原生目标文件。默认输出文件名是CRATE_NAME.o
。
可以使用 -o
标志设置输出的文件名。可以使用 -C extra-filename
标志添加后缀到文件名中。除非使用该 --out-dir
标志,否则文件将被写入当前目录。每种生成类型还可以使用KIND=PATH
形式来指定输出的文件名,该文件名优先于-o
标志。
--print
:打印编译器信息
该标志输出有关编译器的各种信息。可以多次指定此标志,并按照指定标志的顺序打印信息。指定--print
标志通常将禁用 --emit
步骤,并且只会打印所请求的信息。有效的打印值类型为:
crate-name
— crate的名称。file-names
—由link
生成种类创建的文件的名称。sysroot
— sysroot的路径。cfg
— CFG值列表。有关cfg值的更多信息,请参见条件编译。target-list
—已知目标列表。可以使用--target
标志选择目标 。target-cpus
—当前目标的可用CPU值列表。可以使用该-C target-cpu=val
标志选择目标CPU 。target-features
—当前目标的可用目标功能列表。可以使用该-C target-feature=val
标志启用目标功能。该标志是不安全的。有关更多详细信息,请参见已知问题。relocation-models
—重定位模型列表。可以使用-C relocation-model=val
标志选择重定位模型。code-models
—代码模型列表。可以使用-C code-model=val
标志选择代码模型 。tls-models
—支持的线程本地存储(Thread Local Storage)模型列表。可以通过-Z tls-model=val
标志选择模型。native-static-libs
—在创建staticlib
crate类型时可以使用它。如果这是唯一标志,它将执行完整编译并包含诊断说明,该诊断说明指示在链接生成的静态库时要使用的链接器标志。该注释以文本开头,native-static-libs:
以使其更容易获取输出。
-g
:包括调试信息
-C debuginfo=2
的同义词,更多信息请参见这里。
-O
:优化代码
-C opt-level=2
的同义词,更多信息请参见这里。
-o
:输出的文件名
该标志控制输出文件名。
--out-dir
:写入输出的目录
输出的crate将被写入此目录。如果-o
标志被使用,则忽略此标志。
--explain
:提供错误消息的详细说明
rustc
的每个错误都带有错误代码;这将打印出给定错误的详细说明。
--test
:建立测试工具
编译此crate时,rustc
将忽略main
函数,而是产生一个测试工具。
--target
:选择要构建的目标三元组
这可以控制要生产的目标。
-W
:设置lint warnings
此标志将设置应将哪些lint设置为 warn 级别 。
-A
:设置lint allowed
此标志将设置应将哪些lint设置为 allow 级别 。
-D
:设置lint denied
此标志将设置应将哪些lint设置为 deny 级别 。
-F
:设置lint forbidden
此标志将设置应将哪些lint设置为 forbid 级别 。
-Z
:设置不稳定的选项
此标志将允许您设置rustc的不稳定选项。为了设置多个选项,-Z
标志可以多次使用。例如:rustc -Z verbose -Z time
。使用-Z
指定选项仅在nightly中可用。要查看所有可用选项,请运行:rustc -Z help
。
--cap-lints
:设置最严格的lint等级
此标志使您可以“限制”lint,有关更多信息,请参见此处。
-C
/ --codegen
:代码生成选项
此标志将允许您设置代码生成选项。
-V
/ --version
:打印版本
此标志将打印出rustc
的版本。
-v
/ --verbose
:使用详细输出
与其他标志结合使用时,该标志将产生额外的输出。
--extern
:指定外部库的位置
此标志允许您传递将链接到要构建的crate的外部crate的名称和位置。可以多次指定此标志。值的格式应为CRATENAME=PATH
。
--sysroot
:覆盖系统根目录
“ sysroot”是rustc
寻找Rust发行版附带的crate的地方。该标志允许它被覆盖。
--error-format
:控制错误的产生方式
该标志使您可以控制消息的格式。消息将打印到stderr。有效选项是:
human
—可读的输出。这是默认值。json
—结构化JSON输出。有关更多详细信息,请参见JSON章节。short
—短的单行消息。
--color
:配置输出的颜色
此标志使您可以控制输出的颜色设置。有效选项是:
auto
—如果输出发送到tty,则使用颜色。这是默认值。always
—始终使用颜色。never
—切勿使输出着色。
--remap-path-prefix
:在输出中重新映射源名称
在所有输出中重新映射源路径前缀,包括编译器诊断,调试信息,宏扩展等。它从FROM=TO
形式中获取值, 其中路径前缀等于FROM
的 value 被重写为 TO
的 value。在FROM
自身也可以包含一个=
符号,但TO
价值不得。可以多次指定此标志。
这对于标准化生成产品很有用,例如通过从发出到目标文件中的路径名中删除当前目录。替换纯文本形式,不考虑当前系统的路径名语法。例如--remap-path-prefix foo=bar
将匹配foo/lib.rs
但不匹配 ./foo/lib.rs
。
--json
:配置编译器打印的json消息
当--error-format=json
选项传递给rustc时,所有编译器的诊断输出将以JSON blob的形式发出。该 --json
参数可与一起使用,--error-format=json
以配置JSON Blob包含的内容以及发出的JSON Blob。
使用--error-format=json
编译器时,始终会以JSON blob的形式发出任何编译器错误,但是该--json
标志还可以使用以下选项来自定义输出:
diagnostic-short
-诊断消息的json blob应该使用“简短”呈现,而不是常规的“人为”呈现。这意味着的输出--error-format=short
将嵌入到JSON诊断程序中,而不是默认的--error-format=human
。diagnostic-rendered-ansi
-默认情况下,其rendered
字段中的JSON Blob 将包含诊断的纯文本呈现。该选项改为指示诊断程序应具有嵌入的ANSI颜色代码,该颜色代码打算用于rustc通常已经用于终端输出的方式来对消息进行着色。请注意,此选项可与板条箱有效地结合使用,例如fwdansi
将Windows上的这些ANSI代码转换为控制台命令,或者strip-ansi-escapes
如果您以后希望有选择地去除ansi颜色的话。artifacts
-这指示rustc为每个发出的工件发出JSON Blob。工件与--emit
CLI参数中的请求相对应,并且该工件在文件系统上可用时,将立即发出通知。
请注意,它是无效的结合--json
论点与--color
论据,并且需要结合起来--json
使用--error-format=json
。
有关更多详细信息,请参见JSON章节。
@path
:从路径加载命令行标志
如果@path
在命令行上指定,则它将打开path
并从中读取命令行选项。这些选项是每行一个。空行表示空选项。该文件可以使用Unix或Windows样式的行尾,并且必须编码为UTF-8。
1.2 - Rustc的条件编译
介绍
什么是条件编译?
条件编译是指根据某些条件来决定特性代码是否被视为源代码的一部分。
可以使用属性 cfg 和 cfg_attr,还有内置 cfg 宏来有条件地编译源代码。这些条件基于:已编译的crate的目标体系结构,传递给编译器的任意值,以及其他一些杂项。
配置谓词
条件编译的每种形式都采用评估加过为 true 或 false 的配置谓词(configuration predicate)。谓词是以下之一:
- 配置选项。如果设置了该选项,则为true;如果未设置,则为false。
- all() 用逗号分隔的配置谓词列表。只要有一个谓词为false,则为false。如果没有谓词,那就是true。
- any() 用逗号分隔的配置谓词列表。如果至少一个谓词为true,则为true。如果没有谓词,则为false。
- not() 带有配置谓词。如果其谓词为false,则为true;如果其谓词为true,则为false。
配置选项
配置选项是已设置或未设置的 name 和 key-value 对。name 被写为单个标识符,例如 unix
。key-value 对被写为标识符,=,然后是字符串。例如,target_arch = "x86_64"
是一个配置选项。
注意:等号周围的空格将被忽略。
foo="bar"
和foo = "bar"
是等同的配置选项。
键在键值配置选项集中不是唯一的。例如,feature = "std"
和 feature = "serde"
可以同时设置。
设置配置选项
设置哪些配置选项是在板条箱的编译过程中静态确定的。某些选项在编译器集合根据有关统计数据。其他选项是任意设置,基于代码外传递给编译器的输入进行设置。无法从正在编译的板条箱的源代码中设置配置选项。
注意:对于
rustc
,可以使用--cfg
标志设置任意设置的配置选项 。
警告:任意设置的配置选项可能与编译器设置的配置选项具有相同的值。例如,可以rustc --cfg "unix" program.rs
在编译到Windows目标时进行,并同时设置unix
和windows
配置选项。实际执行此操作是不明智的。
#[cfg]
是 Rust 的特殊属性,,允许基于传递给编译器的标记来编译代码。有两种形式:
#[cfg(foo)]
#[cfg(bar = "baz")]
所有的条件编译都由通过cfg配置实现,cfg支持any、all、not等逻辑谓词组合。
它还有一些帮助选项:
#[cfg(any(unix, windows))]
#[cfg(all(unix, target_pointer_width = "32"))]
#[cfg(not(foo))]
这些选项可以任意嵌套:
#[cfg(any(not(unix), all(target_os="macos", target_arch = "powerpc")))]
至于如何启用和禁用这些开关,如果使用Cargo的话,可以在 Cargo.toml
中的[features\]
部分设置:
[features]
# no features by default
default = []
# The “secure-password” feature depends on the bcrypt package.
secure-password = ["bcrypt"]
这时,Cargo会传递给 rustc
一个标记:
--cfg feature="${feature_name}"
这些cfg
标记集合会决定哪些功能被启用,哪些代码会被编译。让我们看看这些代码:
#[cfg(feature = "foo")]
mod foo {
}
如果你用cargo build --features "foo"
编译,他会向rustc
传递--cfg feature="foo"
标记,并且输出中将会包含mod foo
。如果我们使用常规的cargo build
编译,则不会传递额外的标记,因此,(输出)不会存在foo
模块。
cfg_attr
你也可以通过一个基于cfg
变量的cfg_attr
来设置另一个属性:
#[cfg_attr(a, b)]
如果a
通过cfg
属性设置了的话这与#[b]
相同,否则不起作用。
cfg!
cfg!
语法扩展也让你可以在你的代码中使用这类标记:
if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
println!("Think Different!");
}
这会在编译时被替换为一个true
或false
,依配置设定而定。
参考资料
- [Conditional compilation@the rust reference](Conditional compilation)
- 条件编译@rust book 中文翻译
- Rust 交叉编译与条件编译总结
2 - Rust Cargo
2.1 - Cargo概述
2.2 - Cargo设置
source设置
Cargo Crates Registry Source设置.
添加source
为了加快 cargo 的下载速度,避免网速慢或者被墙,添加国内源头 ustc ,需修改 ~/.cargo/config
加入以下内容:
[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = 'ustc'
[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"
#registry = "https://mirrors.ustc.edu.cn/crates.io-index"
[http]
check-revoke = false
其中添加 check-revoke = false
是为了解决下面的问题:
warning: spurious network error (2 tries remaining): [6] Couldn't resolve host name (Could not resolve host: crates-io.proxy.ustclug.org)
warning: spurious network error (1 tries remaining): [6] Couldn't resolve host name (Could not resolve host: crates-io.proxy.ustclug.org)
error: failed to download from `https://crates-io.proxy.ustclug.org/api/v1/crates/serde_test/1.0.126/download`
Caused by:
[6] Couldn't resolve host name (Could not resolve host: crates-io.proxy.ustclug.org)
参考资料
代理设置
网络代理问题
执行 make 命令时遇到问题,卡在 git clone 上:
$ make
cargo fetch --locked
Updating git repository `https://github.com/linkerd/prost`
......(速度太慢,卡住过不去)
git代理设置
我平时一般用 ssh 方式操作git,通过设置 ~/.ssh/config
文件的方式来设置代理,如:
# 这里必须是 github.com,因为这个跟我们 clone 代码时的链接有关
Host github.com
# 如果用默认端口,这里是 github.com,如果想用443端口,这里就是 ssh.github.com 详
见 https://help.github.com/articles/using-ssh-over-the-https-port/
#HostName ssh.github.com
HostName github.com
User git
# 如果是 HTTP 代理,把下面这行取消注释,并把 proxyport 改成自己的 http 代理的端>口
#ProxyCommand socat - PROXY:127.0.0.1:%h:%p,proxyport=3333
# 如果是 socks5 代理,则把下面这行取消注释,并把 23456 改成自己 socks5 代理的端口
ProxyCommand nc -v -x 127.0.0.1:23456 %h %p
上面信息中时通过 https 来操作git仓库,所以想着设置 git 的 http 代理,常见的方式是通过 git config --global
命令,支持 http 代理和 socks5 代理:
# 使用 http 代理
git config --global http.proxy http://192.168.0.1:3333
# 使用 socks5 代理
git config --global http.proxy socks5://192.168.0.1:23456
可以在 ~/.gitconfig
中看到设置的结果。
Cargo代理设置
但是,上面的设置只对直接使用 git 命令有效,当使用 cargo 命令时,依然会卡住。
需要为 cargo 单独设置代理,新建或打开文件 ~/.cargo/config
,使用 http 代理:
[http]
proxy = "192.168.0.1:3333"
[https]
proxy = "192.168.0.1:3333"
使用 socks5代理:
[http]
proxy = "socks5://192.168.0.1:23456"
[https]
proxy = "socks5://192.168.0.1:23456"
2.3.1 - Rust Cargo Book之指南
https://doc.rust-lang.org/cargo/guide/index.html
2.1 为什么cargo会存在
前言
在Rust中,你可能知道,一个库或可执行程序被称为 crate。Crate 是用Rust编译器rustc来编译的。当开始使用Rust时,大多数人遇到的第一个源代码是古老的 “hello world” 程序,他们通过直接调用rustc来编译它。
$ rustc hello.rs
$ ./hello
Hello, world!
注意,上面的命令要求我们明确指定文件名。如果我们要直接使用 rustc 来编译一个不同的程序,就需要用不同的命令行来调用。如果我们需要指定任何特定的编译器标志或包括外部依赖,那么需要的命令将更加具体(和详细)。
此外,大多数非微不足道的程序可能会有对外部库的依赖性,因此也会对其依赖性进行转接。获得所有必要的依赖的正确版本,并保持它们的更新,如果用手工完成,将是费力和容易出错的。
我们可以通过引入更高级别的 “package/包” 抽象和使用包管理器来避免执行上述任务所涉及的手工繁琐工作,而不是仅仅使用 crates 和 rustc。
进入:Cargo
Cargo是Rust的包管理器。它是一个允许Rust包声明其各种依赖关系的工具,并确保你总是得到一个可重复的构建。
为了实现这个目标,Cargo做了四件事:
- 引入两个元数据文件,包含各种包的信息。
- 获取并构建软件包的依赖项。
- 调用 rustc 或其他具有正确参数的构建工具来构建你的软件包。
- 引入惯例,使Rust包的工作更容易。
在很大程度上,Cargo将构建一个特定程序或库所需的命令规范化;这是上述约定的一个方面。正如我们在后面所展示的,同一个命令可以用来构建不同的工件,不管它们的名字是什么。与其直接调用rustc,我们不如调用一些通用的东西,比如 cargo build,让cargo来考虑构建正确的rustc调用。此外,Cargo会自动从注册表中获取我们为工件定义的任何依赖关系,并根据需要安排将其纳入我们的构建中。
可以说,一旦你知道如何构建一个基于Cargo的项目,你就知道如何构建所有的项目,这只是一个小小的夸张。
2.2 创建一个新包
要用Cargo启动一个新的软件包,请使用cargo new:
$ cargo new hello_world --bin
我们传递 --bin
是因为我们在制作二进制程序:如果我们在制作库,我们会传递 --lib
。这也会默认初始化一个新的 git 仓库。如果你不希望它这么做,可以传递 --vcs none
。
让我们看看Cargo为我们生成了什么:
$ cd hello_world
$ tree .
.
├── Cargo.toml
└── src
└── main.rs
1 directory, 2 files
让我们仔细看看Cargo.toml。
[package]
name = "hello_world"
version = "0.1.0"
edition = "2018"
[dependencies]
这被称为 manifest / 清单,它包含了Cargo编译你的包时需要的所有元数据。这个文件是以TOML格式(发音为/tɑməl/)编写的。
下面是 src/main.rs 中的内容:
fn main() {
println! ("Hello, world!");
}
Cargo为我们生成了一个 “hello world " 程序,也就是所谓的二进制crate。让我们来编译它。
$ cargo build
Compiling hello_world v0.1.0 (file:///path/to/package/hello_world)
然后运行它:
$ ./target/debug/hello_world
Hello, world!
我们也可以用cargo run来编译,然后运行,一步到位(如果你在上次编译后没有做任何改动,你就不会看到编译这一行):
$ cargo run
Compiling hello_world v0.1.0 (file:///path/to/package/hello_world)
Running `target/debug/hello_world`
Hello, world!
现在你会注意到一个新文件,Cargo.lock。它包含了关于我们的依赖关系的信息。因为我们还没有任何依赖,所以它不是很有趣。
一旦你准备好发布,你可以使用cargo build –release来编译你的文件,并打开优化功能。
$ cargo build --release
Compiling hello_world v0.1.0 (file:///path/to/package/hello_world)
cargo build --release
将生成的二进制文件放在 target/release
而不是 target/debug
中。
在调试模式下编译是默认的开发模式。由于编译器不做优化,所以编译时间更短,但代码的运行速度会更慢。发布模式的编译时间更长,但代码的运行速度会更快。
2.3 在现有的Cargo包上工作
如果你下载了一个现有的使用Cargo的包,那就很容易开始工作了。
首先,从某处获取该包。在这个例子中,我们将使用从GitHub上的存储库中克隆的rand:
$ git clone https://github.com/rust-lang-nursery/rand.git
$ cd rand
要构建,请使用 cargo build。
$ cargo build
Compiling rand v0.1.0 (file:///path/to/package/rand)
这将获取所有的依赖项,然后与软件包一起构建它们。
2.4 依赖
crates.io 是Rust社区的中央软件包注册中心,作为发现和下载软件包的地点。Cargo的配置是默认使用它来寻找所需的软件包。
要依赖crates.io上的库,请将其添加到你的Cargo.toml中。
添加依赖项
如果你的 Cargo.toml
还没有 [dependencies]
部分,请添加它,然后列出你想要使用的 crate 名称和版本。这个例子添加了对 time
crate 的依赖:
[dependencies]
time = "0.1.12"
版本字符串要求是 semver (语义化)版本。在指定依赖关系的文档中,有更多关于你在这里的选项的信息。
如果我们还想添加对 regex
crate 的依赖,我们就不需要为列出的每个 crate 添加 [dependencies]
。下面是你的整个 Cargo.toml
文件中对 time 和 regex crate 的依赖情况:
[package]
name = "hello_world"
version = "0.1.0"
edition = "2018"
[dependencies]
time = "0.1.12"
regex = "0.1.41"
重新运行 cargo build
,Cargo会获取新的依赖项和所有的依赖项,将它们全部编译,并更新 Cargo.lock
:
$ cargo build
Updating crates.io index
Downloading memchr v0.1.5
Downloading libc v0.1.10
Downloading regex-syntax v0.2.1
Downloading memchr v0.1.5
Downloading aho-corasick v0.3.0
Downloading regex v0.1.41
Compiling memchr v0.1.5
Compiling libc v0.1.10
Compiling regex-syntax v0.2.1
Compiling memchr v0.1.5
Compiling aho-corasick v0.3.0
Compiling regex v0.1.41
Compiling hello_world v0.1.0 (file:///path/to/package/hello_world)
我们的 Cargo.lock 包含了所有这些依赖的确切信息,即我们使用了哪个版本。
现在,如果 regex
被更新,我们仍然会用相同的版本进行构建,直到我们选择 cargo update
。
现在你可以在 main.rs 中使用 regex 库了。
使用 regex::Regex。
use regex::Regex;
fn main() {
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
println!("Did our date match? {}", re.is_match("2014-01-01"));
}
运行它将显示:
$ cargo run
Running `target/hello_world`
Did our date match? true
2.5 包的布局
Cargo使用约定俗成的文件放置方式,使其更容易深入到一个新的Cargo包中:
.
├── Cargo.lock
├── Cargo.toml
├── src/
│ ├── lib.rs
│ ├── main.rs
│ └── bin/
│ ├── named-executable.rs
│ ├── another-executable.rs
│ └── multi-file-executable/
│ ├── main.rs
│ └── some_module.rs
├── benches/
│ ├── large-input.rs
│ └── multi-file-bench/
│ ├── main.rs
│ └── bench_module.rs
├── examples/
│ ├── simple.rs
│ └── multi-file-example/
│ ├── main.rs
│ └── ex_module.rs
└── tests/
├── some-integration-tests.rs
└── multi-file-test/
├── main.rs
└── test_module.rs
Cargo.toml
和Cargo.lock
存放在包的根目录下(package root)。- 源代码放在
src
目录中。 - 默认的库文件是
src/lib.rs
。 - 默认的可执行文件是
src/main.rs
。- 其他可执行文件可以放在
src/bin/
目录中。
- 其他可执行文件可以放在
- 基准文件放在
benches
目录下。 - 示例放在
examples
目录中。 - 集成测试放在
tests
目录中。
如果binary、example、bench或集成测试由多个源文件组成,请将 main.rs
文件与额外的模块一起放在 src/bin
、examples
、benches
或 tests
目录的子目录中。可执行文件的名称将是该目录的名称。
你可以在书中了解更多关于Rust的模块系统。
参见配置目标以了解手动配置目标的更多细节。关于控制Cargo如何自动推断目标名称的更多信息,请参见目标自动发现。
2.6 Cargo.toml vs Cargo.lock
Cargo.toml 和 Cargo.lock 有两种不同的用途。在我们谈论它们之前,这里有一个总结:
- Cargo.toml 是广义的描述你的依赖关系,由你来写。
- Cargo.lock 包含了关于你的依赖关系的确切信息。它是由 Cargo 维护的,不应该被手动编辑。
如果你正在构建一个非最终产品,比如一个其他 rust 包会依赖的 rust 库,把 Cargo.lock 放在你的 .gitignore 中。如果你正在构建一个终端产品,如可执行的命令行工具或应用程序,或一个系统库,其 crate-type 为 staticlib 或 dylib,请将 Cargo.lock 放入git中。如果你想知道为什么会这样,请看FAQ中的 “为什么二进制文件有Cargo.lock的版本控制,而库没有?"。
让我们再深入了解一下。
Cargo.toml
是一个清单文件,我们可以在其中指定关于我们包的一系列不同的元数据。例如,我们可以说我们依赖另一个包:
[package]
name = "hello_world"
version = "0.1.0"
[dependencies]
rand = { git = "https://github.com/rust-lang-nursery/rand.git" }
这个包只有一个依赖关系,就是对rand库的依赖。在这种情况下,我们已经说明了我们依靠的是住在GitHub上的一个特定的Git仓库。由于我们没有指定任何其他信息,Cargo认为我们打算使用主分支上的最新提交来构建我们的包。
听起来不错吧?有一个问题:如果你今天构建了这个包,然后你把副本发给我,我明天再构建这个包,就会发生一些不好的事情。在这期间,可能会有更多的提交给rand,而我的构建会包括新的提交,而你的则不会。因此,我们会得到不同的版本。这就不好了,因为我们需要可重复的构建。
我们可以在Cargo.toml中加入rev行来解决这个问题:
[dependencies]
rand = { git = "https://github.com/rust-lang-nursery/rand.git", rev = "9f35b8e" }
现在我们的构建将是一样的。但是有一个很大的缺点:现在我们每次要更新我们的库的时候,都要手动考虑SHA-1的问题。这既乏味又容易出错。
进入Cargo.lock。因为它的存在,我们不需要手动跟踪确切的修订。Cargo会帮我们做到这一点。当我们有一个像这样的清单时:
[package]
name = "hello_world"
version = "0.1.0"
[dependencies]
rand = { git = "https://github.com/rust-lang-nursery/rand.git" }
当我们第一次构建时,Cargo会将最新的提交信息写入我们的Cargo.lock中。该文件看起来像这样:
[[package]]
name = "hello_world"
version = "0.1.0"
dependencies = [
"rand 0.1.0 (git+https://github.com/rust-lang-nursery/rand.git#9f35b8e439eeedd60b9414c58f389bdc6a3284f9)",
]
[[package]]
name = "rand"
version = "0.1.0"
source = "git+https://github.com/rust-lang-nursery/rand.git#9f35b8e439eeedd60b9414c58f389bdc6a3284f9"
你可以看到这里有更多的信息,包括我们用来构建的确切版本。现在当你把你的包给别人时,他们会使用完全相同的SHA,尽管我们没有在Cargo.toml中指定它。
当我们准备选择使用新版本的库时,Cargo可以重新计算依赖关系并为我们更新:
$ cargo update # 更新所有的依赖项
$ cargo update -p rand # 只更新 "rand"。
这将写出一个新的 Cargo.lock
,其中包含新的版本信息。请注意,cargo update
的参数实际上是一个软件包ID规范,而 rand
只是一个简短的规范。
2.7 测试
Cargo 可以通过 cargo test
命令来运行测试。Cargo会在两个地方寻找要运行的测试:每个 src
文件中的测试和 tests/
中的任何测试。src
文件中的测试应该是单元测试,而 tests/
中的测试应该是集成测试。因此,你需要将你的 crate 导入到测试的文件中。
下面是一个在我们的软件包中运行cargo test的例子,它目前没有测试:
$ cargo test
Compiling rand v0.1.0 (https://github.com/rust-lang-nursery/rand.git#9f35b8e)
Compiling hello_world v0.1.0 (file:///path/to/package/hello_world)
Running target/test/hello_world-9c2b65bbb79eabce
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
如果我们的包有测试,我们会看到更多的输出,有正确的测试数量。
你也可以通过传递一个过滤器来运行一个特定的测试:
$ cargo test foo
这将运行任何名称中含有 foo 的测试。
cargo test
还可以运行额外的检查。它将编译你所包含的任何例子,也将测试你文档中的例子。请参阅Rust文档中的测试指南以了解更多细节。
2.9 Cargo Home
“Cargo home” 的功能是下载和源码缓存。当构建一个 crate 时,Cargo会将下载的构建依赖项存储在 Cargo home 中。你可以通过设置 CARGO_HOME 环境变量来改变 Cargo 主页的位置。如果你在你的 Rust crate 中需要这个信息,home crate 提供了一个API来获取这个位置。默认情况下,Cargo home 位于 $HOME/.cargo/
。
请注意,Cargo home的内部结构并不稳定,可能随时会有变化。
Cargo home 由以下部分组成:
文件:
-
config.toml
create 的全局配置文件,见参考文献中的config条目。 -
credentials.toml
来自cargo login的私人登录凭证,以便登录到注册表。 -
.crates.toml
这个隐藏文件包含了通过cargo install安装的crates的软件包信息。请不要用手去编辑!
备注: 新版本下目录不是这个样子的……
目录:
-
bin
目录包含了通过cargo install
或rustup
安装的 crate 的可执行文件。为了能够访问这些二进制文件,请将该目录的路径添加到你的$PATH
环境变量中。 -
git
Git的源代码存放在这里:-
git/db
当 crate 依赖 git 仓库时,Cargo 会将该仓库作为裸仓库克隆到这个目录,并在必要时进行更新。 -
git/checkouts
如果使用的是 git source,所需的 repo 提交会从 git/db 中的裸repo中签出到这个目录。这为编译器提供了包含在该依赖提交的 repo 中的实际文件。同一个 repo 的不同提交可以进行多次签出。
-
-
registry
crate注册表(如crates.io)的包和元数据都在这里。-
registry/index
索引是一个裸露的 git 仓库,包含了注册中心所有可用板条的元数据(版本、依赖关系等)。 -
registry/cache
下载的依赖关系被存储在缓存中。crates是压缩的gzip文件,以.crate为扩展名。 -
registry/src
如果下载的.crate归档文件是软件包所需要的,它将被解压到registry/src文件夹中,rustc将在那里找到.rs文件。
-
缓存CI中的Cargo home
为了避免在持续集成过程中重新下载所有的 crate 依赖项,你可以缓存 $CARGO_HOME
目录。然而,缓存整个目录往往是低效的,因为它将包含两次下载的源代码。如果我们依赖像 serde 1.0.92
这样的 crate 并缓存整个 $CARGO_HOME
,我们实际上会缓存两次源代码,在 registry/cache
中的 serde-1.0.92.crate
和 registry/src
中的 serde 的提取.rs文件。这可能会不必要地减慢构建速度,因为下载、提取、重新压缩和重新上传缓存到CI服务器可能需要一些时间。
在整个构建过程中,只缓存以下目录就足够了:
bin/
registry/index/
registry/cache/
git/db/
清除缓存
理论上说,你可以随时删除缓存的任何部分,如果一个crate需要源代码,Cargo会尽力恢复这些源代码,方法是重新解压归档或检查裸露的repo,或者直接从网上重新下载源代码。
另外,cargo-cache
crate提供了一个简单的CLI工具,可以只清除缓存中选定的部分,或者在命令行中显示其组件的大小。
2.10 构建缓存
Cargo 将构建的输出存储在 “target” 目录下。默认情况下,这是位于工作区根部的名为target
的目录。要改变这个位置,你可以设置 CARGO_TARGET_DIR
环境变量、build.target-dir
配置值,或者 --target-dir
命令行标志。
目录布局取决于你是否使用 --target
标志为特定的平台进行构建。如果没有指定 --target
,Cargo 会在为主机架构构建的模式下运行。输出会进入目标目录的根部,并根据是否是发布版构建而分开。
目录 | 描述 |
---|---|
target/debug/ |
包含调试构建输出。 |
target/release/ |
包含发行版的构建输出(有 --release 标志)。 |
当用 --target
为另一个目标构建时,输出会被放在一个带有目标名称的目录中:
目录 | 范例 |
---|---|
target/<triple>/debug/ |
target/thumbv7em-none-eabihf/debug/ |
target/<triple>/release/ |
target/thumbv7em-none-eabihf/release/ |
注意: 如果不使用
--target
,这将导致 Cargo 与构建脚本和 proc 宏共享你的依赖项。RUSTFLAGS 将与每个 rustc 调用共享。如果使用--target
标志,构建脚本和进程宏会单独构建(针对主机架构),并且不共享 RUSTFLAGS。
在配置文件目录(debug
或release
)中,工件被放置在以下目录中。
目录 | 描述 |
---|---|
target/debug/ |
包含正在构建的软件包的输出([[bin]]可执行文件和 [lib]‘库目标)。 |
target/debug/examples/ |
包含实例 ([[example]] targets). |
Some commands place their output in dedicated directories in the top level of the target
directory: 有些命令将它们的输出放在 “target” 目录顶层的专用目录中:
目录 | 描述 |
---|---|
target/doc/ |
包含rustdoc文档 (cargo doc ). |
target/package/ |
包含cargo package 和cargo publish 命令的输出。 |
Cargo还创建了其他一些构建过程中需要的目录和文件。它们的布局被认为是Cargo内部的,并且会被改变。这些目录中的一些是。
Directory | Description |
---|---|
target/debug/deps/ |
Dependencies and other artifacts. |
target/debug/incremental/ |
rustc incremental output, a cache used to speed up subsequent builds. |
target/debug/build/ |
Output from build scripts. |
Dep-info文件
在每个编译的工件旁边都有一个后缀为 .d
的文件,叫做 “dep info” 文件。这个文件是一个类似于 Makefile 的语法,表明重建工件所需的所有文件依赖关系。这些文件旨在与外部构建系统一起使用,以便它们能够检测到 Cargo 是否需要被重新执行。文件中的路径默认是绝对的。参见 build.dep-info-basedir 配置选项来使用相对路径。
共享的缓存
第三方工具 sccache
可以用来在不同的工作空间中共享构建的依赖关系。
要设置 sccache
,请用 cargo install sccache
安装它,并在调用Cargo前将 RUSTC_WRAPPER
环境变量设为 ccache
。如果你使用bash,在 .bashrc
中添加 export RUSTC_WRAPPER=sccache
是很有意义的。另外,你也可以在Cargo配置中设置 build.rustc-wrapper
。更多细节请参考sccache文档。
2.3.2 - Cargo Reference笔记: 依赖
https://doc.rust-lang.org/cargo/reference/index.html
3.1 指定依赖关系
crates 可以依赖 crates.io 或其他注册表、git 存储库或本地文件系统中的子目录中的其他库。你也可以暂时覆盖一个依赖的位置–例如,能够测试出你正在本地工作的依赖中的错误修复。你可以为不同的平台设置不同的依赖关系,以及只在开发期间使用的依赖关系。让我们来看看如何做到这些。
从crates.io中指定依赖项
Cargo的配置是默认在 crates.io 上寻找依赖项。在这种情况下,只需要名称和一个版本字符串。在 crate 指南中,我们指定了对 time create 的依赖:
[dependencies]
time = "0.1.12"
字符串 “0.1.12” 是 semver (语义化)版本要求。由于这个字符串中没有任何运算符,它被解释为与我们指定 “^0.1.12” 的方式相同,这被称为关照需求(caret requirement)。
Caret Requirement
关照需求(caret requirement)允许 SemVer 兼容更新到一个指定的版本。如果新的版本号没有修改主要、次要、补丁分组中最左边的非零数字,则允许更新。在这种情况下,如果我们运行 cargo update -p time
,如果是最新的 0.1.z
版本,cargo应该将我们更新到 0.1.13
版本,但不会将我们更新到 0.2.0
。如果我们将版本字符串指定为 ^1.0
,如果是最新的 1.y
版本,cargo应该更新到 1.1
,但不是 2.0
。0.0.x
版本不被认为与其他任何版本兼容。
下面是一些更多的关照需求的例子,以及允许使用这些要求的版本:
^1.2.3 := >=1.2.3, <2.0.0
^1.2 := >=1.2.0, <2.0.0
^1 := >=1.0.0, <2.0.0
^0.2.3 := >=0.2.3, <0.3.0
^0.2 := >=0.2.0, <0.3.0
^0.0.3 := >=0.0.3, <0.0.4
^0.0 := >=0.0.0, <0.1.0
^0 := >=0.0.0, <1.0.0
这个兼容性约定与 SemVer 在对待1.0.0之前的版本的方式不同。SemVer 说 1.0.0 之前没有兼容性,而 Cargo 认为 0.x.y
与 0.x.z
兼容,其中 y≥z
且 x>0
。
Tilde Requirement
Tilde Requirement指定了最小版本并具有一定的更新能力。如果你指定一个主要的、次要的和补丁的版本,或者只指定一个主要的和次要的版本,那么只允许补丁级别的改变。如果你只指定一个主要的版本,那么次要的和补丁级的改变是允许的。
~1.2.3
是Tilde Requirement的例子:
~1.2.3 := >=1.2.3, <1.3.0
~1.2 := >=1.2.0, <1.3.0
~1 := >=1.0.0, <2.0.0
通配符要求
通配符要求允许通配符定位的任何版本。
*
、1.*
和1.2.*
是通配符要求的例子。
* := >=0.0.0
1.* := >=1.0.0, <2.0.0
1.2.* := >=1.2.0, <1.3.0
注意
crates.io不允许有裸*版本。比较要求
比较要求允许手动指定一个版本范围或一个确切的版本来依赖。
下面是一些比较要求的例子:
>= 1.2.0
> 1
< 2
= 1.2.3
多重要求
如上面的例子所示,多个版本的要求可以用逗号分开,例如,>=1.2
,<1.5
。
指定来自其他注册表的依赖关系
要从 crates.io 以外的注册中心指定一个依赖关系,首先必须在 .cargo/config.toml
文件中配置该注册中心。更多信息请参见注册表文档。在依赖关系中,将 registry
的键值设置为要使用的注册表的名称。
[dependencies]
some-crate = { version = "1.0", registry = "my-registry" }
注意
crates.io不允许发布依赖其他注册机构的软件包。指定来自 git 仓库的依赖关系
要依赖位于git仓库中的库,你需要指定的最小信息是带有git钥匙的仓库的位置:
[dependencies]
rand = { git = "https://github.com/rust-lang-nursery/rand" }
Cargo会在这个位置获取git仓库,然后在git仓库的任何地方寻找所要求的 crate 的 Cargo.toml(不一定在根部–例如,指定一个工作区的成员crate名称,并设置git为包含该工作区的仓库)。
由于我们没有指定任何其他信息,Cargo假定我们打算使用主分支上的最新提交来构建我们的包。你可以将 git
key 与 rev
、tag
或 branch
键结合起来,指定其他内容。下面是一个指定要使用 next
分支上的最新提交的例子:
[dependencies]
rand = { git = "https://github.com/rust-lang-nursery/rand", branch = "next" }
一旦添加了git依赖,Cargo会将该依赖锁定在当时的最新提交。一旦锁定,新的提交将不会被自动拉下。不过,它们可以通过 cargo update
手动拉下。
请参阅Git认证,以获得关于私有仓库的Git认证的帮助。
注意
crates.io 不允许发布带有git依赖的软件包(git dev-dependencies 被忽略)。请参阅 “多个位置"一节,以获得后备选择。指定路径依赖
随着时间的推移,我们在指南中的 ·hello_world· 包的体积已经大大增加了。我们可能想把它拆成一个单独的 crate 供别人使用。为了做到这一点,Cargo支持路径依赖,通常是在一个仓库中的子程序。让我们先在 hello_world
包中建立一个新的crate:
# 在hello_world/的内部
$ cargo new hello_utils
这将创建一个新的文件夹 hello_utils
,其中的 Cargo.toml
和 src
文件夹已经准备好被配置。为了告诉 Cargo 这一点,打开 hello_world/Cargo.toml
,将 hello_utils
添加到你的依赖项中。
[dependencies]
hello_utils = { path = "hello_utils" }
这就告诉Cargo,我们依赖一个叫 hello_utils
的crate,这个crate在 hello_utils
文件夹中(相对于它所写的 Cargo.toml
)。
就这样了! 下一次 crate 构建将自动构建 hello_utils
和它自己的所有依赖项,其他人也可以开始使用这个crate。然而,crates.io 上不允许使用只指定路径的依赖项。如果我们想发布我们的 hello_world
crate,我们需要将 hello_utils
的一个版本发布到crates.io,并在依赖项中指定其版本。
[dependencies]
hello_utils = { path = "hello_utils", version = "0.1.0" }
注意
crates.io 不允许发布带有路径依赖的包(路径 dev-dependencies 被忽略)。请参阅 “多个位置” 一节,以获得后备选择。多个位置
可以同时指定注册表版本和 git
或 path
位置。git
或 path
依赖将在本地使用(在这种情况下,version
会与本地副本进行核对),而当发布到 crates.io 这样的注册表时,它将使用注册表的版本。其他组合是不允许的。例子:
[dependencies]
# 在本地使用时使用`my-bitflags`,发布时使用 crates.io 的 1.0 版本。
bitflags = { path = "my-bitflags", version = "1.0" }
# 在本地使用时使用给定的git repo,发布时使用crates.io的1.0版本。
smallvec = { git = "https://github.com/servo/rust-smallvec", version = "1.0" }
# 注意:如果版本不匹配,Cargo将无法编译!
一个有用的例子是,当你把一个库拆成多个包放在同一个工作区时。你可以使用路径依赖来指向工作区中的本地包,以便在开发过程中使用本地版本,然后在发布后使用 crates.io 版本。这类似于指定一个覆盖,但只适用于这一个依赖声明。
特定平台的依赖性
特定平台的依赖关系采用相同的格式,但被列在 target
部分。通常情况下,类似Rust的 #[cfg]语法
将被用来定义这些部分:
[target.'cfg(windows)'.dependencies]
winhttp = "0.4.0"
[target.'cfg(unix)'.dependencies]
openssl = "1.0.1"
[target.'cfg(target_arch = "x86")'.dependencies]
native = { path = "native/i686" }
[target.'cfg(target_arch = "x86_64")'.dependencies]
native = { path = "native/x86_64" }
和Rust一样,这里的语法支持 not
、any
和 all
操作符,以组合各种 cfg name/value 对。
如果你想知道哪些 cfg 目标在你的平台上可用,在命令行中运行 rustc --print=cfg
。如果你想知道哪些cfg目标在其他平台上可用,比如64位Windows,可以运行 rustc --print=cfg --target=x86_64-pc-windows-msvc
。
linux平台的执行结果:
$ rustc --print=cfg
debug_assertions
target_arch="x86_64"
target_endian="little"
target_env="gnu"
target_family="unix"
target_feature="fxsr"
target_feature="sse"
target_feature="sse2"
target_os="linux"
target_pointer_width="64"
target_vendor="unknown"
unix
与你的 Rust 源代码不同,你不能使用 [target.'cfg(feature = "fancy-feature")'.dependencies]
来添加基于可选特性的依赖关系。请使用 [features]
部分来代替。
[dependencies]
foo = { version = "1.0", optional = true }
bar = { version = "1.0", optional = true }
[features]
fancy-feature = ["foo", "bar"]
这同样适用于 cfg(debug_assertions)
、cfg(test)
和 cfg(proc_macro)
。这些值不会像预期的那样工作,而总是有 rustc --print=cfg
所返回的默认值。目前还没有办法根据这些配置值来添加依赖关系。
除了 #[cfg]
语法外,Cargo还支持列出依赖关系所适用的完整目标:
[target.x86_64-pc-windows-gnu.dependencies]
winhttp = "0.4.0"
[target.i686-unknown-linux-gnu.dependencies]
openssl = "1.0.1"
开发依赖
你可以在你的 Cargo.toml 中添加一个 [dev-dependencies]
部分,其格式相当于[dependencies]
。
[dev-dependencies]
tempdir = "0.3"
Dev-dependencies 在编译软件包时不使用,但用于编译 tests、 examples 和 benchmarks。
这些依赖关系不会传播到其他依赖该软件包的软件包上。
你也可以通过在目标部分的标题中使用 dev-dependencies
而不是 dependencies
来获得特定目标的开发依赖。比如说:
[target.'cfg(unix)'.dev-dependencies]
mio = "0.0.1"
注意
注意:当一个软件包被发布时,只有指定version
的 dev-dependencies
才会被包含在发布的crate中。对于大多数使用情况,发布时不需要 dev-dependencies
,尽管有些用户(如操作系统打包者)可能想在crate中运行测试,所以如果可能的话,提供 version
仍然是有益的。
构建依赖性
您可以在您的构建脚本中依赖其他基于Cargo的 crate 来使用。依赖关系通过清单中的 build-dependencies
部分来声明。
[build-dependencies]
cc = "1.0.3"
你也可以通过在目标部分标题中使用 build-dependencies
而不是 dependencies
来获得特定目标的构建依赖。比如说:
[target.'cfg(unix)'.build-dependencies]
cc = "1.0.3"
在这种情况下,只有当主机平台与指定的目标相匹配时,才会构建该依赖关系。
构建脚本不能访问在 dependencies
或 dev-dependencies
部分列出的依赖项。除非在依赖关系部分列出,否则构建依赖关系对软件包本身也同样不可用。软件包本身和它的构建脚本是分开构建的,所以它们的依赖关系不需要重合。通过为独立的目的使用独立的依赖关系,Cargo可以保持更简单、更干净。
选择特性
如果你依赖的软件包提供了有条件的特性,你可以指定使用哪些特性:
[dependencies.awesome]
version = "1.3.5"
default-features = false # 不包括默认功能,并可选择挑选个别功能
features = ["secure-password", "civet"]
重命名Cargo.toml中的依赖项
在 Cargo.toml 中编写 [dependencies]
部分时,你为依赖关系编写的键通常与你在代码中导入的crate的名称一致。但对于某些项目,你可能希望在代码中用不同的名字来引用crate,而不管它在 crates.io 上是如何发布的。例如,你可能希望:
- 避免在Rust源代码中需要
use foo as bar
。 - 依赖于一个 crate 的多个版本。
- 依赖于不同注册中心的同名crate。
为了支持这一点,Cargo在 [dependencies]
部分支持包的 key,即应该依赖哪个包:
[package]
name = "mypackage"
version = "0.0.1"
[dependencies]
foo = "0.1"
bar = { git = "https://github.com/example/project", package = "foo" }
baz = { version = "0.1", registry = "custom", package = "foo" }
在这个例子中,你的Rust代码中现在有三个crate可用:
extern crate foo; // crates.io
extern crate bar; // git repository
extern crate baz; // registry `custom`
这三个crate在它们自己的 Cargo.toml 中都有 foo
的包名,所以我们明确地使用 package key 来通知Cargo我们想要foo的包,尽管我们在本地调用了其他东西。如果没有指定 package 的 key,则默认为被请求的依赖关系的名称。
注意,如果你有一个可选的依赖关系,比如:
[dependencies]
bar = { version = "0.1", package = 'foo', optional = true }
你依赖于 crates.io 的 crate foo,但你的 crate 有一个 bar 特性而不是 foo 特性。也就是说,当重命名时,特性的名称会跟随依赖关系的名称,而不是软件包的名称。
启用反式依赖的工作原理与此类似,例如我们可以在上述清单中添加以下内容:
[features]
log-debug = ['bar/log-debug'] # using 'foo/log-debug' would be an error!
3.1.1 覆盖依赖
覆盖依赖关系
覆盖依赖关系的愿望可以通过多种情况产生。然而,大多数情况下,都可以归结为在 crates.io 上发布之前就能使用一个crate。比如说:
- 你正在开发的一个 crate 也被用于你正在开发的一个更大的应用程序中,你想在这个更大的应用程序中测试一个库的错误修复。
- 一个你不负责的上游代码库在其 git 仓库的主分支上有一个新的功能或错误修复,你想测试一下。
- 你即将发布一个新的主要版本,但你想在整个软件包中进行集成测试,以确保新的主要版本能够正常运行。
- 你已经为你发现的一个bug向上游 crate 提交了一个修复,但你想立即让你的应用程序开始依赖于 crate 的固定版本,以避免bug修复被合并时的阻塞。
这些情况可以通过 [patch] 清单部分
来解决。
本章将介绍几种不同的使用情况,并详细介绍覆盖依赖关系的不同方法。
测试错误修复
假设你正在使用 uuid crate,但当你在工作中发现了一个错误。不过,你很有进取心,所以你决定也尝试修复这个错误。最初,你的清单是这样的:
[package]
name = "my-library"
version = "0.1.0"
[dependencies]
uuid = "1.0"
我们要做的第一件事是通过以下方式在本地克隆 uuid 仓库:
$ git clone https://github.com/uuid-rs/uuid
接下来我们将编辑my-library的清单,使其包含:
[patch.crates-io]
uuid = { path = "../path/to/uuid" }
这里我们声明,我们正在给源代码 crates-io
打上一个新的依赖关系。这将有效地把本地签出的 uuid
版本添加到我们本地软件包的 crates.io 注册表中。
接下来我们需要确保我们的锁文件被更新以使用这个新版本的 uuid
,这样我们的包就会使用本地签出的副本而不是来自 crates.io
的。[patch]
的工作方式是,它将在 ../path/to/uuid
加载依赖关系,然后当 crates.io
被查询 uuid
的版本时,它也会返回本地版本。
这意味着本地检出的版本号很重要,会影响到补丁是否被使用。我们的清单声明 uuid="1.0"
,这意味着我们只解析 >=1.0.0,<2.0.0
,而且Cargo的贪婪解析算法也意味着我们会解析到该范围内的最大版本。通常情况下,这并不重要,因为 git 仓库的版本已经大于或符合 crates.io 上发布的最大版本,但记住这一点很重要!
在任何情况下,通常你现在需要做的就是:
$ cargo build
Compiling uuid v1.0.0 (.../uuid)
Compiling my-library v0.1.0 (.../my-library)
Finished dev [unoptimized + debuginfo] target(s) in 0.32 secs
就这样了,您现在正在使用本地版本的 uuid 进行构建(注意构建输出中括号中的路径)。如果你没有看到本地路径的版本被构建,那么你可能需要运行 cargo update -p uuid --precise $version
,其中 $version
是本地检查出的 uuid 副本的版本。
一旦你修复了你最初发现的错误,你要做的下一件事可能是把它作为一个拉动请求提交给 uuid crate 本身。一旦你完成了这项工作,你也可以更新 [patch]
部分。[patch]
中的列表就像 [dependencies]
部分一样,所以一旦你的拉动请求被合并,你可以将你的路径依赖改为。
[patch.crates-io]
uuid = { git = 'https://github.com/uuid-rs/uuid' }
使用未发布的次要版本
现在让我们从修复bug到添加功能的过程中转换一下齿轮。当你在 my-library 上工作时,你发现 uuid crate 中需要一个全新的功能。你已经实现了这个功能,用 [patch]
在本地进行了测试,并提交了一个拉动请求。让我们来看看在实际发布之前,你如何继续使用和测试它。
我们还假设 crates.io 上 uuid 的当前版本是 1.0.0,但此后 git 仓库的 master 分支已经更新到 1.0.1。这个分支包括你之前提交的新特性。为了使用这个仓库,我们将编辑我们的 Cargo.toml,使其看起来像:
[package]
name = "my-library"
version = "0.1.0"
[dependencies]
uuid = "1.0.1"
[patch.crates-io]
uuid = { git = 'https://github.com/uuid-rs/uuid' }
请注意,我们对 uuid 的本地依赖已经更新到了 1.0.1,因为一旦发布 crate,我们就需要这个版本。不过这个版本在 crates.io 上并不存在,所以我们在清单的 [patch] 部分提供了它。
现在,当我们的库被构建时,它会从 git 仓库中获取 uuid 并解析到仓库中的 1.0.1,而不是试图从 crates.io 上下载一个版本。一旦1.0.1在crates.io上发布,[patch]
部分就可以被删除。
值得注意的是,[patch]
也是过渡性应用。假设你在一个更大的软件包中使用 my-library,例如:
[package]
name = "my-binary"
version = "0.1.0"
[dependencies]
my-library = { git = 'https://example.com/git/my-library' }
uuid = "1.0"
[patch.crates-io]
uuid = { git = 'https://github.com/uuid-rs/uuid' }
请记住,[patch]
是过渡性的,但只能在顶层定义,所以我们 my-library 的消费者必须在必要时重复 [patch]
部分。不过在这里,新的 uuid crate 同时适用于我们对 uuid 的依赖和 my-library -> uuid 的依赖。uuid crate 将被解析为整个 crate 图的一个版本,即 1.0.1,并将从 git 仓库中提取。
2.3.3 - Cargo Reference笔记: 清单
https://doc.rust-lang.org/cargo/reference/manifest.html
3.2 清单格式
每个软件包的Cargo.toml文件被称为其清单。它是以TOML格式编写的。每个清单文件由以下部分组成。
[package] 部分
Cargo.toml
的第一个部分是 [package]
.
[package]
name = "hello_world" # the name of the package
version = "0.1.0" # the current version, obeying semver
authors = ["Alice <a@example.com>", "Bob <b@example.com>"]
Cargo要求的唯一字段是 name 和 version。如果发布到注册表,注册表可能需要额外的字段。关于发布到crates.io的要求,见下面的注释和发布章节。
name字段
包的 name 是一个用来指代包的标识符。它在被列为另一个包的依赖关系时使用,并作为推断的lib和bin目标的默认名称。
name 必须只使用字母数字字符或 -
或 _
,而且不能为空。请注意,cargo new
和 cargo init
对包名施加了一些额外的限制,比如强制要求它是一个有效的Rust标识符,而不是一个关键字。crates.io 甚至施加了更多的限制,比如强制要求只能使用ASCII字符,不能使用保留名,不能使用 “nul” 这样的特殊Windows名称,不能太长,等等。
version字段
Cargo采用了语义版本控制(Semantic Versioning)的概念,因此请确保遵循一些基本规则。
- 在你达到1.0.0之前,什么都可以做,但如果你做了破坏性的修改,就要递增次要版本。在Rust中,破坏性改动包括向结构体添加字段或向枚举添加变体。
- 在1.0.0之后,只有在增加主版本的时候才可以进行破坏性修改。不要破坏构建。
- 1.0.0之后,不要在补丁级版本中添加任何新的公共API(没有新的pub东西)。如果你添加任何新的pub结构、特征、字段、类型、函数、方法或其他东西,一定要增加次要版本。
- 使用有三个数字部分的版本号,如1.0.0而不是1.0。
authors字段
可选的 authors 字段列出了被认为是包的 “作者” 的人或组织。确切的含义是可以解释的–它可以列出包的原始或主要作者、当前的维护者或所有者。一个可选的电子邮件地址可以包含在每个作者条目末尾的斜方括号内。
这个字段只在软件包元数据和 build.rs 中的 CARGO_PKG_AUTHORS
环境变量中出现。它不显示在crates.io的用户界面上。
edition字段
edition
是一个可选的键,它影响你的包是用哪个Rust版本编译的。在 [package]
中设置 edition key 会影响到包中的所有 target/crates,包括测试套件、基准、二进制文件、例子等等。
[package]
# ...
edition = '2021'
大多数清单的版本字段都由 cargo new 自动填入最新的稳定版本。默认情况下,cargo new 创建的清单目前使用的是 2021 版。
如果 Cargo.toml
中没有 edition 字段,那么为了向后兼容,会假定为2015版。请注意,用 cargo new
创建的所有清单都不会使用这种历史回退,因为它们会明确指定版本为较新的值。
rust-version字段
rust-version字段是一个可选的键,它告诉cargo你的包可以用哪个版本的Rust语言和编译器进行编译。如果当前选择的Rust编译器的版本比声明的版本早,cargo会退出,并告诉用户需要什么版本。
第一个支持这个字段的Cargo版本是随着Rust 1.56.0发布的。在旧版本中,这个字段将被忽略,Cargo会显示警告
[package]
# ...
rust-version = "1.56"
Rust版本必须是由两到三个部分组成的裸版本号;它不能包括 semver 操作符或预发布标识符。编译器的预发布标识符,如 -nightly
,在检查Rust版本时将被忽略。rust-version
必须等于或高于首次引入配置版本的版本。
rust-version
可以使用 --ignore-rust-version
选项来忽略。
在 [package]
中设置 rust-version
键将影响到包中的所有 target/crate,包括测试套件、基准、二进制文件、示例等。
description字段
description 是关于软件包的简短介绍。crates.io
会在你的软件包中显示这个描述。这应该是纯文本(不是Markdown)。
[package]
# ...
description = "A short description of my package"
documentation字段
documentation 字段指定了存放 crate 文档的网站的一个 URL。如果清单文件中没有指定 URL,crates.io 将自动将您的 crate 链接到相应的 docs.rs 页面。
[package]
# ...
documentation = "https://docs.rs/bitflags"
readme字段
readme字段应该是包根部的一个文件的路径(相对于这个Cargo.toml
),其中包含关于包的一般信息。crates.io 会将其解释为 Markdown 并在crate的页面上呈现。
[package]
# ...
readme = "README.md"
如果没有指定这个字段的值,并且在软件包根中存在一个名为 README.md、README.txt 或 README 的文件,那么将使用该文件的名称。你可以通过把这个字段设置为false来抑制这种行为。如果该字段被设置为 “true”,将假定默认值为README.md。
homepage字段
homepage字段应该是一个网站的URL,这个网站是你的包的主页:
[package]
# ...
homepage = "https://serde.rs/"
repository字段
repository字段应该是软件包的源存储库的URL:
[package]
# ...
repository = "https://github.com/rust-lang/cargo/"
license和license-file字段
license字段包含软件包发布时使用的软件许可名称。license-file字段包含了一个包含许可证文本的文件的路径(相对于这个Cargo.toml)。
crates.io 将许可证字段解释为 SPDX 2.1
许可证表达式。该名称必须是 SPDX许可证列表3.11
中的一个已知许可证。目前不支持括号。更多信息请参见SPDX网站。
SPDX许可证表达式支持AND和OR运算符来组合多个许可证。
[package]
# ...
license = "MIT OR Apache-2.0"
使用 OR 表示用户可以选择任何一个许可证。使用 AND 表示用户必须同时遵守两个许可证。使用 WITH 运算符表示一个有特殊例外的许可证。一些例子:
- MIT OR Apache-2.0
- LGPL-2.1-only AND MIT AND BSD-2-Clause
- GPL-2.0-or-later WITH Bison-exception-2.2
如果一个软件包使用的是非标准的许可证,那么可以指定 license-file 字段来代替 license 字段。
[package]
# ...
license-file = "LICENSE.txt"
注意
crates.io 需要设置 license 或 license-file。keywords字段
keywords 字段是一个描述此包的字符串数组。当在注册表上搜索该包时,这可以提供帮助,你可以选择任何可以帮助别人找到这个crate的词语。
[package]
# ...
keywords = ["gamedev", "graphics"]
注意
crates.io 最多有5个关键词。每个关键词必须是ASCII文本,以字母开头,并且只包含字母、数字、_
或-
,并且最多拥有20个字符。
categories字段
categories字段是一个字符串数组,表示该包属于的类别:
categories = ["command-line-utilities", "development-tools::cargo-plugins"]
注意
crates.io 最多有5个类别。每个类别都应该与 https://crates.io/category_slugs 中的一个匹配,并且必须完全匹配。workspace字段
workspace字段可以用来配置这个包的工作空间。如果没有指定,这将被推断为文件系统中第一个带有 [workspace]
的 Cargo.toml
向上。如果成员不在工作区根目录下,设置这个字段很有用。
[package]
# ...
workspace = "path/to/workspace/root"
如果清单中已经定义了 [workspace]
表,则不能指定此字段。也就是说,一个 crate 不能既是一个 workspace 的根板块(包含 [workspace]
),又是另一个 workspace 的成员 crate(包含 package.workspace
)。
欲了解更多信息,请参见工作空间一章。
build字段
build字段在包根中指定了一个文件,该文件是用于构建本地代码的构建脚本。更多信息可以在构建脚本指南中找到。
[package]
# ...
build = "build.rs"
默认是 “build.rs”,它从软件包根目录下一个名为 build.rs
的文件中加载该脚本。使用 build = "custom_build_name.rs"
来指定一个不同文件的路径,或者 build = false
来禁止自动检测构建脚本。
links字段
links字段指定了被链接的本地库的名称。更多信息可以在构建脚本指南的链接部分找到。
[package]
# ...
links = "foo"
exclude和include字段
exclude 和 include 字段可以用来明确指定在打包发布项目时包括哪些文件,以及某些类型的变更跟踪(如下所述)。在 exclude 字段中指定的模式确定了一组不包括的文件,而 include 中的模式则指定了明确包括的文件。你可以运行 cargo package --list
来验证哪些文件将被包含在软件包中。
[package]
# ...
exclude = ["/ci", "images/", ".*"]
[package]
# ...
include = ["/src", "COPYRIGHT", "/examples", "!/examples/big_example"]
如果这两个字段都没有被指定,默认情况是包括软件包根部的所有文件,除了下面列出的排除项。
如果没有指定 include
,那么下面的文件将被排除。
- 如果软件包不在git仓库中,所有以点开始的 “隐藏 “文件将被跳过。
- 如果软件包在git仓库中,任何被仓库和全局git配置的gitignore规则所忽略的文件都将被跳过。
不管是指定exclude还是include,以下文件总是被排除在外:
- 任何子包将被跳过(任何包含Cargo.toml文件的子目录)。
- 在包的根目录下名为target的目录将被跳过。
以下文件总是被包括在内:
- 包本身的Cargo.toml文件总是被包含,它不需要被列在include中。
- 如果软件包包含二进制或示例目标,则会自动包含一个最小化的Cargo.lock,更多信息请参见cargo package。
- 如果指定了 license-file,它总是被包括在内。
这些选项是相互排斥的;设置 include 会覆盖 exclude。如果你需要对一组 include 文件进行排除,请使用下面描述的 !
操作符。
这些模式应该是 gitignore 风格的模式。简而言之:
TODO: 这里内容太细了,跳过
publish字段
publish字段可以用来防止一个包被错误地发布到包注册中心(如crates.io),例如,在一个公司中保持一个包的私有性:
[package]
# ...
publish = false
该值也可以是一个字符串数组,这些字符串是允许被发布到的注册表名称:
[package]
# ...
publish = ["some-registry-name"]
如果publish数组包含单个注册表,当没有指定 --registry
标志时,crate 发布命令将使用它。
元数据表
默认情况下,Cargo 会对 Cargo.toml 中未使用的键进行警告,以帮助检测错别字之类的。而 package.metadata
表则完全被Cargo忽略,不会被警告。这一部分可以用于那些想在Cargo.toml
中存储软件包配置的工具。比如说:
[package]
name = "..."
# ...
# Metadata used when generating an Android APK, for example.
[package.metadata.android]
package-name = "my-awesome-android-app"
assets = "path/to/static"
在工作区层面也有一个类似的表,名为 workspace.metadata
。虽然 crate 没有为这两个表的内容指定格式,但建议外部工具可能希望以一致的方式使用它们,例如,如果 package.metadata
中缺少数据,就引用 workspace.metadata
中的数据,如果这对有关工具来说是合理的。
default-run字段
清单 [package]
部分中的 default-run
字段可用于指定 crate 运行所选的默认二进制文件。例如,当同时存在 src/bin/a.rs
和 src/bin/b.rs 时
:
[package]
default-run = "a"
3.2.1 Cargo Target
Cargo包由 target 组成,这些 target 对应于可以被编译成 crate 的源文件。包可以有库、二进制、example、测试和 benchmark target。target 列表可以在 Cargo.toml 清单中配置,通常由源文件的目录布局自动推断出来。
有关配置目标设置的详情,请参见下面的配置目标。
Library/库
库目标定义了 “library/库”,可以被其他库和可执行文件使用和链接。文件名默认为 src/lib.rs
,库的名称默认为软件包的名称。包只能有一个库。库的设置可以在 Cargo.toml 的 [lib]
表中自定义。
# Example of customizing the library in Cargo.toml.
[lib]
crate-type = ["cdylib"]
bench = false
Binaries/二进制文件
二进制目标是经过编译后可以运行的可执行程序。默认的二进制文件名是 src/main.rs
,它默认为软件包的名称。其他二进制文件存储在 src/bin/
目录中。每个二进制文件的设置可以在 Cargo.toml 中的 [[bin]]
表中进行自定义。
二进制文件可以使用包的库的公共API。它们也会与 Cargo.toml 中定义的 [dependencies]
链接。
你可以用带有 --bin <bin-name>
选项的 cargo run
命令来运行单个二进制文件。cargo install
可以用来将可执行文件复制到一个公共位置。
# Example of customizing binaries in Cargo.toml.
[[bin]]
name = "cool-tool"
test = false
bench = false
[[bin]]
name = "frobnicator"
required-features = ["frobnicate"]
示例
位于 examples
目录下的文件是该库所提供的功能的使用示例。当编译时,它们被放置在 target/debug/examples
目录下。
示例可以使用包的库的公共API。它们也与 Cargo.toml 中定义的 [dependencies]
和 [dev-dependencies]
连接。
默认情况下,示例是可执行的二进制文件(有一个 main()
函数)。你可以指定 crate-type
字段来使示例被编译为库。
[[example]]
name = "foo"
crate-type = ["staticlib"]
可以使用 cargo run
命令,并使用 --example <example-name>
选项来运行单个可执行的示例。库中的示例可以用带 --example <example-name>
选项的 cargo build
来构建。带 --example <example-name>
选项的 cargo install
可以用来将可执行的二进制文件复制到一个共同的位置。默认情况下,示例是由 cargo test
编译的,以保护它们不被咬坏。如果你的例子中有 #[test]
函数,而你想用 cargo test
来运行的话,请把 test
字段设置为true。
测试
在Cargo项目中,有两种形式的测试:
- 单元测试是位于库或二进制文件(或任何有
test
字段的 target)中标有#[test]
属性的函数。这些测试可以访问位于它们所定义的目标中的私有API。 - 集成测试是一个单独的可执行二进制文件,也包含
#[test]
函数,它与项目的库相连,可以访问其公共API。
测试是通过 cargo test
命令运行的。默认情况下,Cargo 和 rustc 使用 libtest harness,它负责收集带有 #[test]
属性注释的函数并并行执行,报告每个测试的成功和失败。
集成测试
位于 tests
目录下的文件是集成测试。当你运行 cargo test
时,Cargo会把这些文件中的每一个都编译成一个单独的crate,并执行它们。
集成测试可以使用包的库的公共API。它们也会与 Cargo.toml
中定义的 [dependencies]
和 [dev-dependencies]
相连接。
如果你想在多个集成测试中共享代码,你可以把它放在一个单独的模块中,比如 test/common/mod.rs
,然后把 mod common;
放在每个测试中来导入它。
每个集成测试会生成一个单独的可执行二进制文件,crate 测试将连续运行它们。在某些情况下,这可能是低效的,因为它可能需要更长的时间来编译,而且在运行测试时可能无法充分利用多个CPU。如果你有很多集成测试,你可能要考虑创建一个单一的集成测试,并将测试分成多个模块。libtest 线束会自动找到所有 #[test]
注释的函数,并并行运行它们。你可以向 crate 测试传递模块名称,以便只运行该模块内的测试。
如果有集成测试,二进制目标会自动构建。这允许集成测试执行二进制文件以锻炼和测试其行为。CARGO_BIN_EXE_<name>
环境变量在集成测试建立时被设置,以便它可以使用 env 宏来定位可执行文件。
基准
基准提供了一种使用 cargo bench
命令来测试代码性能的方法。它们遵循与测试相同的结构,每个基准函数都用 #[bench]
属性来注释。与测试类似:
- 基准被放置在
benches
目录下。 - 在库和二进制文件中定义的基准函数可以访问它们所定义的目标中的私有API。benches 目录中的基准可以使用公共API。
- bench 字段可以用来定义哪些目标是默认的基准。
- harness 字段可以用来禁用内置的harness。
注意
目前#[bench]
属性还不稳定,只在 nightly 频道中可用。crates.io
上有一些软件包,可能有助于在稳定频道上运行基准,比如Criterion。
配置目标
Cargo.toml中所有的 [lib]
、[[bin]]
、[[example]]
、[[test]]
和 [[bench]]
部分都支持类似的配置,以指定目标应该如何构建。像 [[bin]]
这样的双括号部分是TOML的数组表,这意味着你可以写一个以上的 [[bin]]
部分来在你的 crate
中制作多个可执行文件。你只能指定一个库,所以 [lib]
是一个普通的TOML表。
下面是每个目标的TOML设置的概述,每个字段在下面有详细描述:
[lib]
name = "foo" # The name of the target.
path = "src/lib.rs" # The source file of the target.
test = true # Is tested by default.
doctest = true # Documentation examples are tested by default.
bench = true # Is benchmarked by default.
doc = true # Is documented by default.
plugin = false # Used as a compiler plugin (deprecated).
proc-macro = false # Set to `true` for a proc-macro library.
harness = true # Use libtest harness.
edition = "2015" # The edition of the target.
crate-type = ["lib"] # The crate types to generate.
required-features = [] # Features required to build this target (N/A for lib).
name字段
name 字段指定了目标的名称,它对应于将被生成的工件的文件名。对于一个库来说,这是依赖项用来引用它的板条箱名称。
对于 [lib]
和默认的二进制文件 (src/main.rs
),这默认为包的名称,任何破折号都用下划线代替。对于其他自动发现的目标,它默认为目录或文件名。
除了 [lib]之外
,所有目标都需要这样做。
path字段
路径字段指定了 crate 的源文件的位置,相对于 Cargo.toml 文件。
如果没有指定,将使用基于目标名称的推断路径。
test字段
test字段表示目标是否默认由 crate 测试来测试。对于lib、bins和测试,默认为true。
doctest字段
doctest字段表示文档实例是否默认由cargo test来测试。这只与库有关,对其他部分没有影响。对于库来说,默认为真。
bench字段
bench字段表示目标是否默认由 cargo bench 进行基准测试。对于lib、bins和benchmark,默认为true。
doc字段
doc字段表示目标程序是否默认包含在由 cargo doc 生成的文档中。默认情况下,库和二进制文件为真。
plugin字段
这个字段用于rustc插件,这些插件正在被废弃。
proc-macro字段
proc-macro 字段表示该库是一个过程性的宏(引用)。这只对 [lib] 目标有效。
harness字段
harness 字段表示 --test
标志将被传递给 rustc,它将自动包含 libtest 库,它是收集和运行标有 #[test]
属性的测试或标有 #[bench]
属性的基准的驱动。默认情况下,所有目标都是true。
如果设置为false,那么你要负责定义一个main()函数来运行测试和基准。
无论 harness 是否被启用,测试都有 cfg(test)
条件表达式被启用。
edition字段
edition字段定义了目标将使用的Rust版本。如果没有指定,它默认为 [package]
的版本字段。这个字段通常不应该被设置,它只适用于高级场景,例如将一个大的包逐步过渡到一个新的版本。
Crate-type字段
crate-type 字段定义了将由目标生成的 crate 类型。它是一个字符串数组,允许你为一个目标指定多个 crate 类型。这只能被指定给库和例子。二进制文件、测试和基准总是 “bin” crate类型。默认值是:
Target | Crate Type |
---|---|
Normal library | "lib" |
Proc-macro library | "proc-macro" |
Example | "bin" |
可用的选项有 bin、lib、rlib、dylib、cdylib、staticlib 和 proc-macro。你可以在《Rust参考手册》中阅读更多关于不同crate类型的信息。
required-features字段
required-features字段指定了目标需要哪些特征才能被构建。如果任何所需的功能没有被启用,目标将被跳过。这只与 [[bin]]
、[[bench]]
、[[test]]
和 [[example]]
部分有关,对 [lib]
没有影响。
[features]
# ...
postgres = []
sqlite = []
tools = []
[[bin]]
name = "my-pg-tool"
required-features = ["postgres", "tools"]
目标自动发现
默认情况下,Cargo 会根据文件系统中的文件布局自动确定要构建的目标。目标配置表,如 [lib]
、[[bin]]
、[[test]]
、[[bench]]
或 [[example]]
,可以用来添加不遵循标准目录布局的额外目标。
自动发现目标的功能可以被禁用,这样就只有手动配置的目标会被建立。将 [package]
部分的key autobins
、autoexamples
、autotests
或 autobenches
设置为false将禁用相应目标类型的自动发现。
[package]
# ...
autobins = false
autoexamples = false
autotests = false
autobenches = false
只有在特殊情况下才需要禁用自动发现功能。例如,如果你有一个库,你想要一个名为 bin
的模块,这将会带来一个问题,因为Cargo通常会试图将bin目录下的任何东西编译为可执行文件。下面是这种情况下的一个布局样本:
├── Cargo.toml
└── src
├── lib.rs
└── bin
└── mod.rs
为了防止 Cargo 将 src/bin/mod.rs
推断为可执行文件,请在 Cargo.toml
中设置 autobins = false
来禁止自动发现。
[package]
# …
autobins = false
注意
对于2015版的软件包,如果在Cargo.toml中至少有一个目标是手动定义的,那么自动发现的默认值为false。从2018版开始,默认值始终为 “true”。2.3.4 - Cargo Reference笔记: 工作区
https://doc.rust-lang.org/cargo/reference/workspaces.html
3.3 工作区
工作空间是一个或多个软件包的集合,它们共享共同的依赖性解析(有一个共享的Cargo.lock)、输出目录和各种设置,如配置文件。作为工作区一部分的包被称为工作区成员。有两种类型的工作空间:根包(Root package)或虚拟清单(Virtual manifest)。
根包(Root Package)
通过在 Cargo.toml
中添加 [workspace]
部分可以创建一个工作区。这可以添加到已经定义了 [package]
的 Cargo.toml
中,在这种情况下,该包是工作区的根包(Root package)。工作区根(workspace root)是工作区的 Cargo.toml
所在的目录。
虚拟清单(Virtual Manifest)
另外,在创建 Cargo.toml 文件时,也可以在其中加入 [workspace]
部分,但不加入 [package]
部分。这被称为虚拟清单。这通常适用于没有 “主要 " 软件包的情况,或者您希望将所有软件包放在不同的目录中。
关键特征
工作区的关键点是:
-
所有软件包共享一个共同的
Cargo.lock
文件,该文件驻留在工作区根部。 -
所有软件包共享一个共同的输出目录,该目录默认为工作区根目录下的target。
-
Cargo.toml
中的[patch]
、[replace]
和[profile.*]
部分只在根清单中被识别,而在 crate 的清单中被忽略。
[workspace]部分
Cargo.toml
中的 [workspace]
表定义了哪些软件包是工作区的成员。
[workspace]
members = ["member1", "path/to/member2", "crates/*"]
exclude = ["crates/foo", "path/to/other"]
驻留在工作区目录中的所有路径依赖自动成为成员。其他成员可以用 members 键列出,members 键应该是一个包含 Cargo.toml
文件的目录的字符串数组。
成员列表还支持使用典型的文件名glob模式,如 *
和 ?
exclude key 可以用来防止路径被包含在一个工作区中。如果某些路径的依赖关系根本不希望出现在工作区中,或者使用 glob 模式而你想删除一个目录,这就很有用。
空的 [workspace]
表可以和 [package]
一起使用,以方便地创建一个包含该包和其所有路径依赖的工作区。
工作区选择
当在工作区的子目录内时,Cargo
会自动在父目录中搜索带有 [workspace]
定义的 Cargo.toml
文件,以确定使用哪个工作区。package.workspace
清单键可以在 crate 中用来指向工作空间的根,以覆盖这种自动搜索。如果成员不在工作区根的子目录内,手动设置会很有用。
包的选择
在工作区中,与包相关的 cargo
命令,如 cargo build
,可以使用 -p / --package
或 --workspace
命令行标志来决定对哪些包进行操作。如果没有指定这两个标志,Cargo将使用当前工作目录下的软件包。如果当前目录是一个虚拟工作区,它将适用于所有成员(就像在命令行中指定 --workspace
一样)。
可以指定可选的 default-members
键,以设置在工作区根部和不使用包选择标志时要操作的成员。
[workspace]
members = ["path/to/member1", "path/to/member2", "path/to/member3/*"]
default-members = ["path/to/member2", "path/to/member3/foo"]
当指定时,default-members
必须扩展到一个成员的子集。
Workspace.metadata表
Workspace.metadata
表会被 Cargo
忽略,不会被警告。这一部分可以用于那些想在 Cargo.toml
中存储工作空间配置的工具。比如说。
[workspace]
members = ["member1", "member2"]
[workspace.metadata.webcontents]
root = "path/to/webproject"
tool = ["npm", "run", "build"]
# ...
在 package.metadata
中也有一组类似的表格。虽然 cargo 没有为这两个表的内容指定格式,但建议外部工具可能希望以一致的方式使用它们,例如,如果 package.metadata
中缺少数据,可以参考 workspace.metadata
中的数据,如果这对相关工具来说是有意义的。
2.3.5 - Cargo Reference笔记: 特性
https://doc.rust-lang.org/cargo/reference/features.html
Cargo的 “feature” 提供了一种用来表达条件编译和可选依赖关系的机制。包在 Cargo.toml
的 [features]
表中定义了一组命名的特性,每个特性都可以被启用或禁用。正在构建的包的特性可以在命令行中用 --features
等标志启用。依赖项的特性可以在Cargo.toml
的依赖项声明中启用。
关于如何使用特性的一些例子,也请参见特性示例一章。
[features]部分
在 Cargo.toml
的 [features]
表中定义了特性。每个特性都指定了它所启用的其他特性或可选依赖关系的数组。下面的例子说明了如何将特性用于二维图像处理库,其中对不同图像格式的支持可以选择性地包括在内。
[features]
# 定义了一个名为 "webp"的特性,该特性不启用任何其他特性。
webp = []
定义了这个特性后,cfg表达式可以用来在编译时有条件地包含代码以支持所要求的特性。例如,在包的lib.rs里面可以包括这个:
// 这有条件地包括一个实现WEBP支持的模块。
#[cfg(feature = "webp")]
pub mod webp;
Cargo 使用 rustc --cfg
标志在包中设置特性,代码可以用 cfg
属性或 cfg
宏来测试它们的存在。
特性可以列出要启用的其他特性。例如,ICO图像格式可以包含BMP和PNG图像,所以当它被启用时,应该确保这些其他功能也被启用。
[features]
BMP = []
png = []
ico = ["bmp", "png"]
webp = []
特性名称可以包括 Unicode XID标准
中的字符(包括大多数字母),另外还允许以 _
或数字0到9开头,在第一个字符之后还可以包含-
、+
或.
注意
crates.io 对特性名称的语法施加了额外的限制,即它们必须是 ASCII 字母数字字符或_
、-
或+
。
默认特性
默认情况下,所有的特性都是禁用的,除非明确启用。这可以通过指定默认功能来改变。
[features]
default = ["ico", "webp"]
bmp = []
png = []
ico = ["bmp", "png"]
webp = []
当软件包被构建时,default
特性被启用,这反过来又会启用所列出的特性。这种行为可以通过以下方式改变:
-
--no-default-features
命令行标志禁用软件包的默认特性。 -
可以在依赖关系声明中指定
default-features = false
选项。
注意
在选择默认特性集时要小心。默认特性是一种便利,使用户更容易使用软件包,而不需要强迫用户仔细选择启用哪些常用的特性,但也有一些缺点。除非指定 default-features = false
,否则依赖关系会自动启用默认功能。这就很难保证默认特性不被启用,特别是对于一个在依赖关系图中出现多次的依赖关系。每个包都必须确保指定 default-features = false
来避免启用它们。
另一个问题是,从默认集中删除一个特性可能是SemVer不兼容的改变,所以你应该确信你会保留这些特性。
可选依赖关系
依赖关系可以被标记为"optional",这意味着它们将不会被默认编译。例如,假设我们的2D图像处理库使用一个外部包来处理GIF图像。这可以这样表达:
[dependencies]
gif = { version = "0.11.1", optional = true }
可选依赖关系隐含地定义了一个与依赖关系同名的特性。这意味着可以在代码中使用相同的 cfg(feature = "gif")
语法,并且可以像启用 --features gif
这样的特性一样启用该依赖关系(见下面的命令行特性选项)。
注意
[feature]
表中的特性不能与依赖关系使用相同的名称。实验性地支持启用此功能和其他扩展功能,可通过命名功能在 nightly 频道中使用。
明确定义的特性也可以启用可选的依赖关系。只要在特性列表中包括可选依赖的名称即可。例如,假设为了支持AVIF图像格式,我们的库需要启用另外两个依赖项:
[dependencies]
ravif = { version = "0.6.3", optional = true }
rgb = { version = "0.8.25", optional = true }
[features]
avif = ["ravif", "rgb"]
在这个例子中,avif特性将启用列出的两个依赖项。
注意
另一种包含依赖关系的方法是使用特定平台的依赖关系。与使用特性不同,这些是基于目标平台的条件。依赖关系的特征
依赖关系的特性可以在依赖关系声明中启用。features
key 表示要启用哪些特征:
[dependencies]
# 启用serde的 `derive` 特性。
serde = { version = "1.0.118", features = ["derive"] }
可以使用 default-features = false
来禁用默认的特性:
[dependencies]
flate2 = { version = "1.0.3", default-features = false, features = ["zlib"] }
注意
这可能无法确保默认功能被禁用。如果另一个依赖关系包括 flate2 而没有指定default-features = false
,那么默认特性将被启用。更多细节请参见下面的特性统一。
依赖关系的特性也可以在 [features] 表中启用。语法是 “package-name/feature-name”。例如。
[dependencies]
jpeg-decoder = { version = "0.1.20", default-features = false }
[features]
# 通过启用 jpeg-decoder 的 "rayon" 特性来启用并行处理支持。
parallel = ["jpeg-decoder/rayon"]。
注意
“package-name/feature-name” 语法也将启用 package-name,如果它是一个可选的依赖关系。禁用该行为的实验性支持在 nightly 频道中通过弱依赖性特性提供。命令行特性选项
以下命令行标志可用于控制哪些特性被启用:
-
--features
FEATURES: 启用列出的特性。多个特性可以用逗号或空格分开。如果使用空格,如果从shell中运行Cargo,请确保在所有特性周围使用引号(如--features "foo bar"
)。如果在一个工作区构建多个包,可以使用package-name/feature-name
语法来指定特定工作区成员的特性。 -
--all-features
: 激活在命令行上选择的所有软件包的所有特性。 -
--no-default-features
: 不激活所选软件包的默认特性。
特性统一
特性对于定义它们的包来说是唯一的。在一个包上启用一个特性并不会在其他包上启用同名的特性。
当一个依赖关系被多个包使用时,Cargo 会在构建依赖关系时使用该依赖关系上启用的所有特性的联合。这有助于确保只使用该依赖关系的一个副本。更多细节请参见解析器文档的特性部分。
例如,让我们看一下使用大量特性的 winapi
包。如果你的包依赖于包 foo
,它启用了winapi的 “fileapi” 和 “handleapi” 特性,而另一个依赖关系 bar
启用了 winapi 的 “std” 和 “winnt” 特性,那么 winapi 将在启用所有这四个特性后被构建:
这样做的结果是,特性应该是累加的。也就是说,启用特性不应该使功能失效,而且通常启用任何特性的组合都是安全的。特性不应该引入 SemVer 不兼容的变化。
例如,如果你想选择性地支持 no_std
环境,不要使用 no_std
特性。相反,使用启用 std
的特性。例如:
#![no_std]
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "std")]
pub fn function_that_requires_std() {
// ...
}
相互排斥的特征
在极少数情况下,功能可能会相互不兼容。如果可能的话,应该避免这种情况,因为这需要协调依赖关系图中包的所有用途,以合作避免一起启用它们。如果不可能,可以考虑增加一个编译错误来检测这种情况。例如。
#[cfg(all(feature = "foo", feature = "bar"))]
compile_error!("feature \"foo\" and feature \"bar\" cannot be enabled at the same time");
与其使用相互排斥的特性,不如考虑一些其他的选择:
- 将功能分成独立的包。
- 当有冲突时,选择一个特性而不是另一个。
cfg-if
包可以帮助编写更复杂的cfg
表达式。 - 构建代码以允许这些特性同时启用,并使用运行时选项来控制使用哪一个。例如,使用配置文件、命令行参数或环境变量来选择启用哪种行为。
检查已解决的特性
在复杂的依赖关系图中,有时很难理解不同的特性是如何在不同的软件包上被启用的。cargo tree
命令提供了几个选项来帮助检查和显示哪些特性被启用。一些选项可以试试:
-
cargo tree -e features
: 这将显示依赖关系图中的特性。每个特性会显示哪个软件包启用了它。 -
cargo tree -f "{p} {f}"
: 这是一个更紧凑的视图,显示每个软件包上启用的特性的逗号分隔列表。 -
cargo tree -e features -i foo
: 这将反转树形图,显示特性如何流入给定的软件包 “foo”。这可能很有用,因为查看整个图表可能相当大,而且让人不知所措。当你试图弄清楚哪些特性在一个特定的包上被启用以及为什么被启用时,可以使用这个方法。
特征解析器版本2
可以用 Cargo.toml 中的 resolver
字段指定不同的特性解析器,像这样:
[package]
name = "my-package"
version = "1.0.0"
resolver = "2"
关于指定解析器版本的更多细节,请参见 解析器版本 部分。
版本 “2” 的解析器在少数情况下避免了统一特性,因为这种统一可能是不需要的。确切的情况在解析器章节中有描述,但简而言之,它避免在这些情况下进行统一:
-
对于当前没有被构建的目标,在特定平台依赖项上启用的特性被忽略。
-
构建依赖和 proc-macros 不与普通依赖共享特性。
-
Dev-dependencies 不激活功能,除非构建一个需要这些功能的目标(如测试或示例)。
在某些情况下,避免统一的做法是必要的。例如,如果 build-dependency
启用了 std
特性,而同一依赖关系被用作 no_std
环境的正常依赖关系,启用 std
会破坏构建。
然而,一个缺点是,这可能会增加构建时间,因为依赖关系会被构建多次(每次都有不同的特性)。当使用版本 “2” 解析器时,建议检查那些被多次构建的依赖关系,以减少总体构建时间。如果不需要用单独的特性来构建那些重复的包,可以考虑在依赖关系声明中的特性列表中添加特性,这样重复的包最终就会有相同的特性(因此Cargo只会构建一次)。你可以用 cargo tree --duplicates
命令来检测这些重复的依赖关系。它将显示哪些软件包被多次构建;寻找任何列出相同版本的条目。关于获取已解决特征信息的更多信息,请参见检查已解决特征。对于构建依赖,如果你使用 --target
标志进行交叉编译,则没有必要这样做,因为在这种情况下,构建依赖总是与普通依赖分开构建。
TBD: 后面的内容似乎意思不大,以后再细看。
3 - Rust Module
3.1 - Learn Rust中的Module
https://doc.rust-lang.org/rust-by-example/mod.html
Rust提供了强大的模块系统,可以用来在逻辑单元(模块)中分层分割代码,并管理它们之间的可见性(public/private)。
模块是项目的集合:函数、结构体、特征、impl
块,甚至是其他模块。
可见性
默认情况下,模块中的项目具有私有可见性,但这可以用 pub
修改器来覆盖。只有模块中的 public 项目可以从模块范围之外被访问。
// 名为 `my_mod` 的 module
mod my_mod {
// 模块中的项目默认为 private 可见性。
fn private_function() {
println!("called `my_mod::private_function()`");
}
// 使用`pub`修改器来覆盖默认的可见性。
pub fn function() {
println!("called `my_mod::function()`");
}
// 项目可以访问同一模块中的其他项目,
// 即使是私有的
pub fn indirect_access() {
print!("called `my_mod::indirect_access()`, that\n> ");
private_function();
}
// 模块也可以嵌套
pub mod nested {
pub fn function() {
println!("called `my_mod::nested::function()`");
}
#[allow(dead_code)]
fn private_function() {
println!("called `my_mod::nested::private_function()`");
}
// 使用 `pub(in path)` 语法声明的函数只在给定的路径中可见。
// `path`必须是一个父级或祖级的模块
pub(in crate::my_mod) fn public_function_in_my_mod() {
print!("called `my_mod::nested::public_function_in_my_mod()`, that\n> ");
public_function_in_nested();
}
// 使用 `pub(self)` 语法声明的函数只在当前模块内可见
// 这相当于是 private。
pub(self) fn public_function_in_nested() {
println!("called `my_mod::nested::public_function_in_nested()`");
}
// 使用 `pub(super)` 语法声明的函数只在父模块内可见。
pub(super) fn public_function_in_super_mod() {
println!("called `my_mod::nested::public_function_in_super_mod()`");
}
}
pub fn call_public_function_in_my_mod() {
print!("called `my_mod::call_public_function_in_my_mod()`, that\n> ");
nested::public_function_in_my_mod();
print!("> ");
nested::public_function_in_super_mod();
}
// pub(crate)使函数只在当前的crate内可见。
pub(crate) fn public_function_in_crate() {
println!("called `my_mod::public_function_in_crate()`");
}
// 嵌套模块遵循同样的可见性规则
mod private_nested {
#[allow(dead_code)]
pub fn function() {
println!("called `my_mod::private_nested::function()`");
}
// Private 的父项仍然会限制子项的可见性,
// 即使它被声明为在一个更大的范围内可见。
#[allow(dead_code)]
pub(crate) fn restricted_function() {
println!("called `my_mod::private_nested::restricted_function()`");
}
}
}
fn function() {
println!("called `function()`");
}
fn main() {
// 模块允许在具有相同名称的项目之间进行歧义消除。
function();
my_mod::function();
// public 项目,包括嵌套模块内的项目,都可以从父模块之外访问。
my_mod::indirect_access();
my_mod::nested::function();
my_mod::call_public_function_in_my_mod();
// pub(crate) 项可以从同一create的任何地方调用。
my_mod::public_function_in_crate();
// pub(in path)项只能从指定的模块内调用。
// Error! function `public_function_in_my_mod` is private
//my_mod::nested::public_function_in_my_mod();
// TODO ^ Try uncommenting this line
// 模块的 private 项目不能被直接访问,即使是嵌套在一个 public 模块中。
// Error! `private_function` is private
//my_mod::private_function();
// TODO ^ Try uncommenting this line
// Error! `private_function` is private
//my_mod::nested::private_function();
// TODO ^ Try uncommenting this line
// Error! `private_nested` is a private module
//my_mod::private_nested::function();
// TODO ^ Try uncommenting this line
// Error! `private_nested` is a private module
//my_mod::private_nested::restricted_function();
// TODO ^ Try uncommenting this line
}
结构体的可见性
结构体的字段有一个额外的可见性级别。这种可见性默认为 private
,可以用 pub
修改器来覆盖。这种可见性只在结构体从其定义的模块之外被访问时才有意义,其目的是为了隐藏信息(封装)。
mod my {
// 带有泛型类型 `T` 的 public 字段的 public 结构体
pub struct OpenBox<T> {
pub contents: T,
}
// 带有泛型类型 `T` 的 private 字段的 public 结构体
#[allow(dead_code)]
pub struct ClosedBox<T> {
contents: T,
}
impl<T> ClosedBox<T> {
// A public constructor method
pub fn new(contents: T) -> ClosedBox<T> {
ClosedBox {
contents: contents,
}
}
}
}
fn main() {
// 带有 public 字段的 public 结构体可以像平常一样构建
let open_box = my::OpenBox { contents: "public information" };
// 而它们的字段可以正常访问。
println!("The open box contains: {}", open_box.contents);
// 带有 private 字段的 public 结构不能使用字段名构建。
// Error! `ClosedBox` has private fields
//let closed_box = my::ClosedBox { contents: "classified information" };
// TODO ^ Try uncommenting this line
// 然而,带有 private 字段的结构体可以使用 public 构造函数来创建
let _closed_box = my::ClosedBox::new("classified information");
// 而 public 结构体的 private 字段不能被访问。
// Error! The `contents` field is private
//println!("The closed box contains: {}", _closed_box.contents);
// TODO ^ Try uncommenting this line
}
use
声明
use
声明可以用来将完整的路径绑定到新的名字上,以方便访问。它经常被这样使用:
use crate::deeply::nested::{
my_first_function,
my_second_function,
AndATraitType
};
fn main() {
my_first_function();
}
可以使用 as
关键字将导入的数据绑定到一个不同的名称:
// 将 `deeply::nested::function` 路径绑定到 `other_function`.
use deeply::nested::function as other_function;
fn function() {
println!("called `function()`");
}
mod deeply {
pub mod nested {
pub fn function() {
println!("called `deeply::nested::function()`");
}
}
}
fn main() {
// 访问 `deeply::nested::function` 更简单
other_function();
println!("Entering block");
{
// 这等同于 `use deeply::nested::function as function`.
// 这个 `function()` 将隐藏外部的同名函数
use crate::deeply::nested::function;
// `use` 绑定有本地范围。在这个例子中, `function()` 的隐藏只在当前块中。
function();
println!("Leaving block");
}
function();
}
super和self
super
和 self
关键字可以在路径中使用,以消除访问项目时的歧义,并防止不必要的路径硬编码。
fn function() {
println!("called `function()`");
}
mod cool {
pub fn function() {
println!("called `cool::function()`");
}
}
mod my {
fn function() {
println!("called `my::function()`");
}
mod cool {
pub fn function() {
println!("called `my::cool::function()`");
}
}
pub fn indirect_call() {
// 让我们从这个作用域访问所有名为 `function` 的函数!
print!("called `my::indirect_call()`, that\n> ");
// `self` 关键字指的是当前的模块范围 -- 在这里是`my`。
// 调用 `self::function()` 和直接调用 `function()` 都会得到相同的结果,
// 因为它们引用的是同一个函数。
self::function();
function();
// We can also use `self` to access another module inside `my`:
// 我们也可以使用 `self` 来访问 `my` 中的另一个模块。
self::cool::function();
// `super`关键字指的是父范围(在`my`模块之外)。
super::function();
// 这将绑定到 *crate* 范围内的 `cool::function`。
// 在这种情况下,crate 范围是最外层的范围。
{
use crate::cool::function as root_function;
root_function();
}
}
}
fn main() {
my::indirect_call();
}
文件层次结构
模块可以被映射到文件/目录的层次结构中。让我们来分解一下文件中的可见性例子:
$ tree .
.
|-- my
| |-- inaccessible.rs
| |-- mod.rs
| `-- nested.rs
`-- split.rs
split.rs
的内容:
// 这个声明将寻找一个名为 `my.rs` 或 `my/mod.rs` 的文件,
// 并将其内容插入这个范围内的名为`my`的模块中。
mod my;
fn function() {
println!("called `function()`");
}
fn main() {
my::function();
function();
my::indirect_access();
my::nested::function();
}
my/mod.rs
的内容:
// 同样,`mod inaccessible` 和 `mod nested` 将找到 `nested.rs` 和 `inaccessible.rs` 文件,
// 并将它们插入各自的模块下。
mod inaccessible;
pub mod nested;
pub fn function() {
println!("called `my::function()`");
}
fn private_function() {
println!("called `my::private_function()`");
}
pub fn indirect_access() {
print!("called `my::indirect_access()`, that\n> ");
private_function();
}
my/nested.rs
的内容:
pub fn function() {
println!("called `my::nested::function()`");
}
#[allow(dead_code)]
fn private_function() {
println!("called `my::nested::private_function()`");
}
my/inaccessible.rs
的内容:
#[allow(dead_code)]
pub fn public_function() {
println!("called `my::inaccessible::public_function()`");
}
让我们检查一下事情是否仍然像以前一样工作:
$ rustc split.rs && ./split
called `my::function()`
called `function()`
called `my::indirect_access()`, that
> called `my::private_function()`
called `my::nested::function()`
3.2 - Rust编程语言:使用package,crate和module来管理成长中的项目
3.2.1 - 模块系统
编写大型程序时,组织代码将是很重要的,因为在头脑中保持对整个程序的跟踪将变得不可能。通过对相关功能进行分组,并将具有不同特征的代码分开,你将明确在哪里可以找到实现某个特定功能的代码,以及在哪里可以改变某个功能的工作方式。
到目前为止,我们所写的程序都是在单个文件的单个模块中。随着项目的发展,可以通过把代码分成多个模块,然后再分成多个文件来组织代码。一个 package 可以包含多个二进制 crate,也可以选择一个库 crate。随着 package 的增长,可以将部分内容提取到独立的 crate 中,成为外部依赖。本章涵盖了所有这些技术。对于由一组相互关联的 package 组成的、共同发展的大型项目,Cargo提供了工作空间,我们将在第14章的 “Cargo工作空间” 部分介绍。
除了对功能进行分组外,封装实现细节还可以让你在更高层次上重用代码:一旦你实现了某个操作,其他代码就可以通过代码的 public 接口调用该代码,而不需要知道实现是如何工作的。你写代码的方式定义了哪些部分是 public 的,供其他代码使用,哪些部分是 private 的实现细节,你保留改变的权利。这是另一种限制你必须记在脑子里的细节数量的方法。
scope
是一个相关的概念:编写代码的嵌套上下文有一组被定义为 “in scope” 的名称。在阅读、编写和编译代码时,程序员和编译器需要知道某个特定位置的特定名称是否指的是一个变量、函数、结构体、枚举、模块、常量或其他项目,以及该项目意味着什么。你可以创建作用域并改变哪些名字在作用域内或作用域外。你不能在同一个作用域中有两个同名的项目;有工具可以解决名称冲突。
Rust有很多功能可以让你管理代码组织,包括哪些细节是 public 的,哪些细节是 private 的,以及在程序中每个范围内有哪些名字。这些功能,有时被统称为模块系统,包括:
-
Package: Cargo 的一个功能,可以建立、测试和分享 crate。
-
Crate:一个模块树,产生一个库或可执行文件
-
Module 和 use: 让你控制路径的组织、范围和隐私
-
Path:命名项目的方式,如结构体、函数或模块
在本章中,我们将介绍所有这些功能,讨论它们如何相互作用,并解释如何使用它们来管理范围。到最后,你应该对模块系统有一个扎实的了解,并且能够像专家一样使用作用域。
3.2.2 - Package和Crate
https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html
我们要讨论的模块系统的第一部分是 package 和 crate。crate 是一个二进制文件或库。crate root 是一个源文件,Rust编译器从它开始,构成了你的crate的根模块(我们将在 “定义模块以控制范围和隐私” 部分深入解释模块)。包是一个或多个提供一系列功能的crate。Package 包含一个 Cargo.toml
文件,描述如何构建这些 crate。
一些规则决定了 package 可以包含什么。一个 package 最多可以包含一个库crate。它可以包含任何你想要的二进制crate,但它必须至少包含一个crate(无论是库还是二进制)。
让我们来看看我们创建一个 package 时发生了什么。首先,我们输入 cargo new
命令:
$ cargo new my-project
Created binary (application) `my-project` package
$ ls my-project
Cargo.toml
src
$ ls my-project/src
main.rs
当我们输入命令时,Cargo创建了一个 Cargo.toml
文件,给了我们一个 package。看一下 Cargo.toml
的内容,没有提到 src/main.rs
,因为 Cargo 遵循的惯例是 src/main.rs
是与 package 同名的二进制 crate 的 crate 根。同样地,Cargo 知道如果包的目录中包含 src/lib.rs
,那么该 package 就包含一个与该 package 同名的库 crate,而 src/lib.rs
是其 crate 根。Cargo 会将 crate 根文件传递给 rustc 来构建库或二进制文件。
这里,我们有一个只包含 src/main.rs
的 package,意味着它只包含一个名为 my-project
的二进制 crate。如果一个包包含 src/main.rs
和 src/lib.rs
,那么它就有两个crate:一个库和一个二进制,两者的名字都与包相同。通过在 src/bin
目录中放置文件,一个包可以有多个二进制 crate:每个文件都是一个单独的二进制 crate。
一个 crate 将把相关的功能集中在一个范围内,这样功能就很容易在多个项目之间共享。例如,我们在第二章中使用的 rand
crate 提供了生成随机数的功能。我们可以在自己的项目中使用该功能,方法是将 rand
crate 带入我们项目的作用域。所有由 rand
crate 提供的功能都可以通过 crate 的名称 rand
来访问。
将 crate 的功能保留在自己的范围内,可以明确特定的功能是在我们的 crate 还是rand crate 中定义的,并防止潜在的冲突。例如,rand
crate 提供了一个名为 Rng
的 trait。我们也可以在自己的 crate 中定义一个名为 Rng
的结构。因为 crate 的功能是在它自己的范围内命名的,所以当我们添加 rand
作为依赖关系时,编译器不会对 Rng
这个名字的含义感到困惑。在我们的 crate 中,它指的是我们定义的 Rng
结构体。我们将从 rand
crate 中访问 Rng
的特性,即 rand::Rng
。
让我们继续谈一谈模块系统吧。
3.2.3 - 定义模块以控制范围和隐私
https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html
在这一节中,我们将讨论模块和模块系统的其他部分,即允许你命名项目的路径;将路径带入范围的 use
关键字;以及使项目 public 的 pub
关键字。我们还将讨论 as
关键字、外部包和 glob
操作符。现在,让我们把重点放在模块上吧!
模块让我们把 crate 中的代码组织成一组,以提高可读性并便于重复使用。模块还可以控制项目的私密性,也就是一个项目是可以被外部代码使用(public)还是属于内部实现的细节(private),不能被外部使用。
作为例子,让我们写一个提供餐厅功能的crate。我们将定义函数的签名,但将其主体留空,以集中精力组织代码,而不是在代码中实际实现一个餐厅。
在餐饮业中,餐厅的某些部分被称为前厅,其他部分被称为后厅。前厅是顾客所在的地方;这里是主人为顾客安排座位,服务员接受订单和付款,调酒师调制饮料的地方。后厨是厨师在厨房工作的地方,洗碗工负责清理,经理负责行政工作。
为了按照真正的餐厅的工作方式来组织我们的 crate,我们可以将这些功能组织成嵌套模块。通过运行 cargo new --lib restaurant
创建一个名为 restaurant
的新库;然后将清单 7-1 中的代码放入 src/lib.rs
,以定义一些模块和函数签名。
文件名:src/lib.rs
:
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() {}
}
}
清单 7-1: 包含其他模块的 front_of_house
模块,这些模块又包含函数。
我们以 mod
关键字开始定义模块,然后指定模块的名称(本例中为 front_of_house
),并在模块的主体周围加上大括号。在模块内部,我们可以有其他的模块,如本例中的 hosting
和 serving
模块。模块还可以容纳其他项目的定义,如结构体、枚举、常量、特征,或如清单7-1中的函数。
通过使用模块,我们可以将相关的定义组合在一起,并说明它们为什么相关。使用这段代码的程序员会更容易找到他们想要使用的定义,因为他们可以根据分组来浏览代码,而不必阅读所有的定义。为这段代码添加新功能的程序员会知道该把代码放在哪里,以保持程序的条理性。
早些时候,我们提到 src/main.rs
和 src/lib.rs
被称为 crate roots
。之所以叫这个名字,是因为这两个文件中的任何一个文件的内容都构成了一个名为 crate
的模块,位于 crate
模块结构的根部,也就是所谓的模块树(module tree)。
清单 7-2 显示了清单 7-1 中结构的模块树:
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
清单7-2:清单7-1中代码的模块树
这棵树显示了一些模块是如何相互嵌套的(例如,hosting
嵌套在 front_of_house
里面)。这棵树还显示一些模块是彼此的兄弟姐妹,这意味着它们被定义在同一个模块中(hosting
和 serving
被定义在 front_of_house
中)。为了继续这个家庭隐喻,如果模块A包含在模块B里面,我们就说模块A是模块B的孩子,模块B是模块A的父母。请注意,整个模块树的根在名为crate的隐式模块下。
模块树可能会让你想起你电脑上的文件系统的目录树;这是一个非常恰当的比较!就像文件系统的目录一样。就像文件系统中的目录,你用模块来组织你的代码。就像目录中的文件一样,我们需要一种方法来找到我们的模块。
3.2.4 - 用于引用模块树中项目的路径
https://doc.rust-lang.org/book/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html
为了向 rust 展示在模块树中的哪里找到项目,我们使用路径,就像我们在浏览文件系统时使用路径一样。如果我们想调用一个函数,我们需要知道它的路径。
路径可以有两种形式:
-
绝对路径从 crate root 开始,通过使用 crate name 或字面 crate。
-
相对路径从当前模块开始,使用
self
、super
或当前模块中的标识符。
绝对路径和相对路径后面都有一个或多个标识符,用双冒号(::
)分开。
让我们回到清单7-1中的例子。我们如何调用 add_to_waitlist
函数?这就等于问,add_to_waitlist
函数的路径是什么?在清单7-3中,我们通过删除一些模块和函数来简化我们的代码。我们将展示两种方法,从定义在 crate root 的新函数 eat_at_restaurant
中调用 add_to_waitlist
函数。eat_at_restaurant
函数是我们库中 public API的一部分,所以我们用 pub
关键字标记它。在 “用pub关键字暴露路径” 一节中,我们将详细介绍 pub
。注意,这个例子还不能编译;我们稍后会解释原因。
文件名:src/lib.rs
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
清单7-3:使用绝对和相对路径调用 add_to_waitlist
函数
我们第一次在 eat_at_restaurant
中调用 add_to_waitlist
函数时,使用了一个绝对路径。add_to_waitlist
函数与 eat_at_restaurant
定义在同一个 crate
中,这意味着我们可以使用 crate
关键字来启用绝对路径。
在 crate
之后,我们包括每一个连续的模块,直到我们到达 add_to_waitlist
。你可以想象一个具有相同结构的文件系统,我们会指定路径 /front_of_house/hosting/add_to_waitlist
来运行 add_to_waitlist
程序;使用 crate
名称从 crate root
开始,就像在 shell 中使用 /
从文件系统根开始。
第二次我们在 eat_at_restaurant
中调用 add_to_waitlist
,我们使用了一个相对路径。路径以 front_of_house
开始,它是定义在与 eat_at_restaurant
相同级别的模块树上的模块名称。在这里,文件系统的对应路径是 front_of_house/hosting/add_to_waitlist
。以一个名字开始意味着路径是相对的。
选择使用相对路径还是绝对路径是你根据你的项目做出的决定。这个决定应该取决于你是更倾向于将项目定义代码与使用该项目的代码分开移动,还是一起移动。例如,如果我们将 front_of_house
模块和 eat_at_restaurant
函数移到一个名为 customer_experience
的模块中,我们需要更新 add_to_waitlist
的绝对路径,但相对路径仍然有效。然而,如果我们将 eat_at_restaurant
函数单独移到名为 dining
的模块中,那么调用 add_to_waitlist
的绝对路径将保持不变,但相对路径将需要更新。我们更倾向于指定绝对路径,因为它更有可能使代码定义和项目调用相互独立地移动。
让我们试着编译清单7-3,看看为什么它还不能编译! 我们得到的错误显示在清单7-4中:
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hosting` is private
--> src/lib.rs:9:28
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
error[E0603]: module `hosting` is private
--> src/lib.rs:12:21
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant`
To learn more, run the command again with --verbose.
清单7-4:构建清单7-3中代码的编译器错误
这些错误信息说,模块托管是 private的。换句话说,我们有托管模块和 add_to_waitlist
函数的正确路径,但Rust不会让我们使用它们,因为它不能访问私有部分。
模块不仅对组织你的代码有用。它们还定义了Rust的隐私边界:封装了外部代码不允许知道、调用或依赖的实现细节的那一行。所以,如果你想让一个项目,如函数或结构变得私有,你就把它放在模块里。
Rust中 private 的工作方式是,所有项目(函数、方法、结构体、枚举、模块和常量)默认为私有。父模块中的项不能使用子模块中的私有项,但子模块中的项可以使用其祖先模块中的项。原因是,子模块包裹并隐藏了它们的实现细节,但子模块可以看到它们被定义的上下文。继续用餐厅的比喻,把隐私规则想成是餐厅的后台办公室:里面发生的事情对餐厅的顾客来说是私密的,但是办公室经理可以看到他们经营的餐厅里的一切,并进行操作。
Rust选择让模块系统这样运作,这样隐藏内部的执行细节是默认的。这样,你就知道你可以在不破坏外部代码的情况下改变内部代码的哪些部分。但是你可以通过使用 pub
关键字将一个项目公开,从而将子模块的内部代码暴露给外部祖先模块。
用pub关键字暴露路径
让我们回到清单 7-4 中的错误,告诉我们 hosting
模块是私有的。我们希望父模块中的 eat_at_restaurant
函数能够访问子模块中的 add_to_waitlist
函数,因此我们用 pub
关键字标记 hosting
模块,如清单 7-5 所示。
文件名:src/lib.rs
mod front_of_house {
pub mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
不幸的是,清单7-5中的代码仍然导致了一个错误,如清单7-6所示。
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:9:37
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:12:30
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant`
To learn more, run the command again with --verbose.
清单7-6:构建清单7-5中代码的编译器错误
发生了什么?在 mod hosting 前面添加 pub
关键字,使该模块成为公共模块。有了这个变化,如果我们可以访问 front_of_house
,我们就可以访问 hosting。但是 hosting 的内容仍然是私有的;使模块公开并没有使其内容公开。模块上的 pub
关键字只能让它的祖先模块的代码引用它。
清单 7-6 中的错误说 add_to_waitlist
函数是私有的。隐私规则适用于结构体、枚举、函数和方法以及模块。
让我们也通过在其定义前添加 pub
关键字使 add_to_waitlist
函数成为 public 函数,如清单7-7所示。
文件名:src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
清单7-7:在 mod hosting
和 fn add_to_waitlist
中添加 pub
关键字,让我们从 eat_at_restaurant
中调用该函数。
现在,代码将被编译! 让我们看看绝对路径和相对路径,并仔细检查一下为什么添加 pub 关键字可以让我们在 add_to_waitlist
中使用这些路径,以尊重隐私规则。
在绝对路径中,我们从 crate 开始,它是我们 crate 模块树的根。然后 front_of_house
模块被定义在 crate root 中。front_of_house
模块不是公开的,但是因为 eat_at_restaurant
函数与 front_of_house
定义在同一个模块中(也就是说,eat_at_restaurant
和 front_of_house
是兄弟姐妹),我们可以从 eat_at_restaurant
引用 front_of_house
。接下来是标有 pub 的 hosting 模块。我们可以访问 hosting 的父模块,所以我们可以访问 hosting。最后,add_to_waitlist
函数被标记为pub,我们可以访问它的父模块,所以这个函数的调用是有效的!
在相对路径中,除了第一步外,逻辑与绝对路径相同:路径不是从 crate root 开始,而是从 front_of_house
开始。front_of_house
模块与eat_at_restaurant
定义在同一个模块中,所以从定义 eat_at_restaurant
的模块开始的相对路径起作用。然后,因为 hosting
和 add_to_waitlist
都被标记为pub,其余的路径都可以工作,这个函数调用是有效的!
用super开始相对路径
我们还可以通过在路径的开头使用 super 来构建从父模块开始的相对路径。这就像用 ..
语法来启动文件系统路径。为什么我们要这样做呢?
考虑一下清单7-8中的代码,它模拟了厨师修正错误的订单并亲自把它带给顾客的情况。函数 fix_incorrect_order
通过指定以 super
开头的 serve_order
的路径来调用函数 serve_order
。
文件名: src/lib.rs
fn serve_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::serve_order();
}
fn cook_order() {}
}
清单 7-8: 使用以super开头的相对路径调用函数
fix_incorrect_order
函数在 back_of_house
模块中,所以我们可以使用 super
转到 back_of_house
的父模块,在本例中是 crate,即根。从那里,我们寻找 serve_order
并找到它。成功了! 我们认为 back_of_house
模块和 serve_order
函数可能会保持相同的关系,如果我们决定重新组织crate的模块树,它们会被一起移动。因此,我们使用了super,这样,如果这段代码被移到不同的模块中,我们将有更少的地方需要更新代码。
将结构体和枚举公开
我们也可以使用 pub 来指定结构体和枚举为公共的,但有一些额外的细节。如果我们在结构体定义前使用pub,我们会使结构体成为公共的,但结构体的字段仍然是私有的。我们可以根据具体情况使每个字段公开或不公开。在清单 7-9 中,我们定义了一个 public 的 back_of_house::Breakfast
结构体,其中有一个 public 的烤面包字段,但有一个私有的 seasonal_fruit
字段。这模拟了餐厅的情况,即顾客可以选择随餐的面包类型,但是厨师会根据当季的水果和库存来决定随餐的水果。可用的水果变化很快,所以顾客无法选择水果,甚至无法看到他们会得到哪些水果。
文件名: src/lib.rs
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() {
// Order a breakfast in the summer with Rye toast
let mut meal = back_of_house::Breakfast::summer("Rye");
// Change our mind about what bread we'd like
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
// The next line won't compile if we uncomment it; we're not allowed
// to see or modify the seasonal fruit that comes with the meal
// meal.seasonal_fruit = String::from("blueberries");
}
清单7-9:一个有一些公共字段和一些私有字段的结构体
因为 back_of_house::Breakfast
结构体中的 toast
字段是公共的,在 eat_at_restaurant
中,我们可以使用点符号对 toast
字段进行写入和读取。注意我们不能在 eat_at_restaurant
中使用 seasonal_fruit
字段,因为 seasonal_fruit
是私有的。试着取消修改 seasonal_fruit
字段值的那一行,看看你会得到什么错误。
另外,请注意,因为 back_of_house::Breakfast
有一个私有字段,该结构体需要提供一个公共关联函数来构造一个 Breakfast 的实例(我们在这里将其命名为summer
)。如果 Breakfast 没有这样的函数,我们就不能在 eat_at_restaurant
中创建一个 Breakfast
的实例,因为我们不能在eat_at_restaurant
中设置私有的季节性水果字段的值。
相反,如果我们把一个枚举变成公共的,那么它的所有变体都是公共的。我们只需要在 enum
关键字前加上pub,如清单7-10所示。
文件名: src/lib.rs
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
pub fn eat_at_restaurant() {
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
清单7-10: 将一个枚举指定为公共的,使其所有的变体都是公共的
因为我们把 Appetizer
枚举公开了,所以我们可以在 eat_at_restaurant
中使用 Soup
和 Salad
的变体。除非它们的变体是公共的,否则枚举不是很有用;如果在每种情况下都要用 pub 来注释所有的枚举变体,那会很烦人,所以枚举变体的默认值是公共的。结构体通常在其字段不公开的情况下也很有用,所以结构体字段遵循默认为私有的一般规则,除非用pub来注释。
还有一种涉及 pub 的情况我们没有涉及,那就是我们最后一个模块系统特性:use 关键字。我们将首先介绍 use 本身,然后展示如何结合 pub 和 use。
3.2.5 - 用use关键字将路径纳入范围
https://doc.rust-lang.org/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html
到目前为止,我们所写的调用函数的路径可能看起来很不方便,而且是重复的。例如,在清单7-7中,无论我们选择绝对路径还是相对路径来调用add_to_waitlist
函数,每次我们想调用 add_to_waitlist
时都必须指定 front_of_house
和 hosting
。幸运的是,有一种方法可以简化这个过程。我们可以把一个路径带入一个作用域中,然后用 use
关键字来调用这个路径中的项目,就像它们是本地项目一样。
在清单 7-11 中,我们将 crate::front_of_house::hosting
模块带入 eat_at_restaurant
函数的作用域中,因此我们只需要指定 hosting::add_to_waitlist
来调用 eat_at_restaurant
的 add_to_waitlist
函数。
文件名: src/lib.rs
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();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
清单 7-11: 用 use 将模块带入作用域
在作用域中添加 use
和路径类似于在文件系统中创建符号链接。通过在 crate root 中添加 use crate::front_of_house::hosting
,hosting
现在是该作用域中的一个有效名称,就像 hosting
模块在 crate
根中被定义一样。用 use 带入作用域的路径也检查隐私,就像其他路径一样。
你也可以用 use 和一个相对路径把一个项目带入作用域。清单 7-12 显示了如何指定一个相对路径以获得与清单 7-11 中相同的行为。
文件名: src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use self::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
清单 7-12: 用use和相对路径将模块带入作用域
创建习惯性的使用路径
在清单 7-11 中,你可能想知道为什么我们指定了 use crate::front_of_house::hosting
,然后在 eat_at_restaurant
中调用 hosting::add_to_waitlist
,而不是像清单 7-13 中那样一直指定 use
路径到 add_to_waitlist
函数以达到相同的结果。
文件名: src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting::add_to_waitlist;
pub fn eat_at_restaurant() {
add_to_waitlist();
add_to_waitlist();
add_to_waitlist();
}
清单 7-13: 用 use 把 add_to_waitlist
函数带入作用域,这是不规范的做法
尽管清单 7-11 和 7-13 完成了同样的任务,但清单 7-11 是用 use 将函数带入作用域的习惯性方法。用 use 将函数的父模块带入作用域意味着我们必须在调用函数时指定父模块。在调用函数时指定父模块可以清楚地表明该函数不是本地定义的,同时还可以尽量减少全路径的重复。清单7-13中的代码不清楚add_to_waitlist
是在哪里定义的。
另一方面,当引入结构体、枚举和其他使用的项目时,指定完整的路径是一种习惯做法。清单 7-14 显示了将标准库的 HashMap 结构带入二进制 crate 的范围的习惯性方法。
文件名:src/main.rs
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
清单7-14:以一种习惯性的方式将HashMap带入范围内
这个习惯性背后没有什么强大的理由:这只是已经出现的惯例,人们已经习惯了这样阅读和编写Rust代码。
这个习惯的例外是,如果我们用use语句将两个同名的项目带入作用域,因为Rust不允许这样做。清单7-15显示了如何将两个名字相同但父模块不同的结果类型带入作用域以及如何引用它们。
文件名:src/lib.rs
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
Ok(())
}
fn function2() -> io::Result<()> {
// --snip--
Ok(())
}
清单 7-15: 将两个同名的类型带入同一个作用域需要使用它们的父模块。
正如你所看到的,使用父模块可以区分这两种结果类型。如果我们指定使用 std::fmt::Result
和使用 std::io::Result
,我们就会有两个Result类型在同一个作用域中,Rust就不知道我们使用Result时指的是哪一个。
用 as 关键字提供新的名字
对于用 use 将两个同名的类型带入同一个作用域的问题,还有一个解决办法:在路径之后,我们可以指定 as
和一个新的本地名称,或别名,作为该类型的名称。清单 7-16 显示了另一种编写清单 7-15 中代码的方法,即用 as
重命名两个 Result 类型中的一个。
文件名:src/lib.rs
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
Ok(())
}
fn function2() -> IoResult<()> {
// --snip--
Ok(())
}
在第二个使用语句中,我们为 std::io::Result
类型选择了新的名字 IoResult
,这不会与我们也带入范围的 std::fmt
的 Result 冲突。清单 7-15 和清单 7-16 被认为是习惯性的,所以选择由你决定
用pub的方式重新输出名字
当我们用 use
关键字将一个名字带入作用域时,新作用域中的名字是私有的。为了使调用我们代码的代码能够引用该名称,就像它被定义在该代码的作用域中一样,我们可以结合 pub
和 use
。这种技术被称为"再输出",因为我们把一个项目带入作用域,同时也使这个项目可以被其他人带入他们的作用域。
清单 7-17 显示了清单 7-11 中的代码,根模块中的 use
改为 pub use
。
文件名:src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
清单 7-17: 用pub use使名字可供任何代码从新的作用域中使用
通过使用 pub use
,外部代码现在可以使用 hosting::add_to_waitlist
调用 add_to_waitlist
函数。如果我们没有指定 pub 使用,eat_at_restaurant
函数可以在它的作用域中调用 hosting::add_to_waitlist
,但是外部代码不能利用这个新路径。
当你的代码的内部结构与调用你的代码的程序员对该领域的思考方式不同时,重新输出是很有用的。例如,在这个餐厅的比喻中,经营餐厅的人思考的是 “前厅 “和 “后厨”。但是来餐厅的顾客可能不会用这些术语来思考餐厅的各个部分。通过酒馆的使用,我们可以用一种结构来写我们的代码,但暴露出不同的结构。这样做使我们的库对从事库工作的程序员和调用库的程序员都有很好的组织。
使用外部包
在第二章中,我们编写了一个猜谜游戏项目,该项目使用了一个名为 rand
的外部包来获取随机数。为了在我们的项目中使用 rand
,我们在 Cargo.toml
中添加了这一行。
文件名:Cargo.toml
rand = "0.8.3"
在 Cargo.toml
中把 rand
作为一个依赖项,告诉 Cargo
从 crates.io
下载 rand
包和任何依赖项,使 rand
对我们的项目可用。
然后,为了将 rand
的定义带入我们包的范围,我们添加了一个以 crates.io
的名字为开头的 use
行,并列出了我们想带入范围的项目。回想一下,在第2章的"生成随机数"部分,我们将 Rng
特性带入范围,并调用了 rand::thread_rng
函数。
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..101);
}
Rust社区的成员在 crates.io
上提供了许多包,将它们中的任何一个拉到你的包中都包括这些相同的步骤:在你的包的 Cargo.toml
文件中列出它们,并使用 use
将它们的包中的项目引入范围。
请注意,标准库(std)也是我们包外部的一个crate。因为标准库是和Rust语言一起提供的,所以我们不需要修改 Cargo.toml
来包括std。但我们需要用use
来引用它,把那里的项目引入我们包的范围。例如,对于 HashMap
,我们可以使用这一行。
use std::collections::HashMap;
这是一个以 std 开头的绝对路径,是标准库 crate 的名称。
使用嵌套路径来清理大型使用列表
如果我们使用定义在同一crate或同一模块中的多个项目,将每个项目列在自己的行中会占用我们文件中大量的垂直空间。例如,我们在清单2-4中的猜谜游戏中的这两条使用语句将std中的项目带入范围。
文件名: src/main.rs
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--
相反,我们可以使用嵌套的路径,在一行中把相同的项目纳入范围。我们通过指定路径的共同部分,后面是两个冒号,然后用大括号列出路径中不同的部分,如清单7-18所示。
文件名: src/main.rs
// --snip--
use std::{cmp::Ordering, io};
// --snip--
清单 7-18: 指定嵌套路径,将具有相同前缀的多个项目带入作用域
在更大的程序中,使用嵌套路径将许多项目从同一个crate或模块带入作用域,可以减少很多单独的 use
语句的数量。
我们可以在路径中的任何一级使用嵌套路径,这在合并两个共享子路径的使用语句时很有用。例如,清单 7-19 显示了两条使用语句:一条将 std::io
带入作用域,另一条将 std::io::Write
带入作用域。
文件名:src/lib.rs
use std::io;
use std::io::Write;
清单7-19: 两个使用语句,其中一个是另一个的子路径
这两个路径的共同部分是 std::io
,这就是完整的第一个路径。为了将这两条路径合并为一条使用语句,我们可以在嵌套路径中使用 self
,如清单7-20所示。
文件名:src/lib.rs
use std::io::{self, Write};
清单7-20: 将清单 7-19 中的路径组合成一个 use 语句
这一行将 std::io
和 std::io::Write
带入范围。
Glob 操作符
如果我们想把某个路径中定义的所有公共项目都带入作用域,我们可以在该路径后面指定*
,即glob操作符。
use std::collections::*;
这个 use
语句将所有定义在 std::collection
中的公共项目带入当前的作用域。在使用 glob
操作符的时候要小心! glob
会使你更难分辨哪些名字在作用域中,以及你程序中使用的名字是在哪里定义的。
在测试时,glob
操作符经常被用来将所有被测试的东西带入测试模块;我们将在第11章的 “如何编写测试” 一节中讨论这个问题。glob 操作符有时也作为 prelude 模式的一部分使用:关于这种模式的更多信息,请参见标准库文档。
3.2.6 - 将模块分离到不同的文件中
https://doc.rust-lang.org/book/ch07-05-separating-modules-into-different-files.html
到目前为止,本章中所有的例子都是在一个文件中定义多个模块。当模块变得很大时,你可能想把它们的定义移到一个单独的文件中,以使代码更容易浏览。
例如,让我们从清单7-17中的代码开始,将 front_of_house
模块移到它自己的文件 src/front_of_house.rs
中,改变 crate root 文件,使其包含清单7-21中的代码。在这个例子中,crate root 文件是 src/lib.rs
,但这个过程也适用于 crate root 文件为 src/main.rs
的二进制crate。
文件名:src/lib.rs
mod front_of_house;
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
清单 7-21: 声明 front_of_house
模块,其主体将在 src/front_of_house.rs
中。
而 src/front_of_house.rs
则从 front_of_house
模块的主体中获取定义,如清单 7-22 所示。
文件名:src/front_of_house.rs
pub mod hosting {
pub fn add_to_waitlist() {}
}
清单 7-22: src/front_of_house.rs
中 front_of_house
模块内部的定义
在 mod front_of_house
后面使用分号,而不是使用块,是告诉 Rust 从与该模块同名的另一个文件中加载该模块的内容。为了继续我们的例子并将 hosting 模块也提取到自己的文件中,我们将 src/front_of_house.rs
改为只包含 hosting 模块的声明。
文件名:src/front_of_house.rs
pub mod hosting;
然后我们创建一个 src/front_of_house
目录和一个 src/front_of_house/hosting.rs
文件,以包含 hosting 模块中的定义。
文件名:src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}
模块树保持不变,eat_at_restaurant
中的函数调用将不做任何修改,即使定义在不同的文件中。这种技术可以让你在模块体积增大时将其移到新的文件中。
注意 src/lib.rs
中的 pub use crate::front_of_house::hosting
语句也没有改变,使用也不会对哪些文件作为 crate
的一部分被编译产生任何影响。mod 关键字声明了模块,Rust 会在与模块同名的文件中寻找进入该模块的代码。
总结
Rust允许你把包分成多个crate,把crate 分成模块,这样你就可以从另一个模块引用一个模块中定义的项目。你可以通过指定绝对或相对路径来做到这一点。这些路径可以被带入使用语句的范围内,这样你就可以在该范围内使用一个较短的路径来多次使用该项目。模块代码默认是私有的,但你可以通过添加pub关键字使定义公开。
3.3 - 对Rust的模块系统的清晰解释
https://www.sheshbabu.com/posts/rust-module-system/
Rust的模块系统出乎意料地令人困惑,给初学者带来了很多挫折感。
在这篇文章中,我将用实际的例子来解释模块系统,这样你就能清楚地了解它是如何工作的,并能立即开始在你的项目中应用它。
由于Rust的模块系统相当独特,我要求读者以开放的心态阅读这篇文章,并抵制将其与其他语言的模块工作方式进行比较。
让我们用这个文件结构来模拟一个真实世界的项目。
my_project
├── Cargo.toml
└─┬ src
├── main.rs
├── config.rs
├─┬ routes
│ ├── health_route.rs
│ └── user_route.rs
└─┬ models
└── user_model.rs
这些是我们应该能够消费我们的模块的不同方式:
这3个例子应该足以解释Rust的模块系统如何工作。
例子1
让我们从第一个例子开始–在 main.rs
中导入 config.rs
。
// main.rs
fn main() {
println!("main");
}
// config.rs
fn print_config() {
println!("config");
}
每个人都会犯的第一个错误是,仅仅因为我们有 config.rs
、health_route.rs
等文件,我们就认为这些文件是模块,我们可以从其他文件导入它们。
下面是我们看到的(文件系统树)和编译器看到的(模块树)。
令人惊讶的是,编译器只看到 crate 模块,也就是我们的 main.rs
文件。这是因为我们需要在Rust中明确建立模块树–文件系统树和模块树之间没有隐式映射。
我们需要在Rust中显式建立模块树,没有隐式映射到文件系统中。
要将一个文件添加到模块树中,我们需要使用 mod 关键字将该文件声明为一个子模块。接下来让人困惑的是,你会认为我们在同一个文件中声明一个文件为模块。但我们需要在不同的文件中声明!因为我们只有 main.rs
。由于我们在模块树中只有 main.rs
,让我们把 config.rs
声明为 main.rs
中的一个子模块。
mod 关键字声明一个子模块
mod关键字的语法是这样的:
mod my_module;
这里,编译器会在同一目录下寻找 my_module.rs
或 my_module/mod.rs
。
my_project
├── Cargo.toml
└─┬ src
├── main.rs
└── my_module.rs
or
my_project
├── Cargo.toml
└─┬ src
├── main.rs
└─┬ my_module
└── mod.rs
由于 main.rs
和 config.rs
在同一个目录下,让我们把 config
模块声明如下。
// main.rs
mod config;
fn main() {
config::print_config();
println!("main");
}
// config.rs
fn print_config() {
println!("config");
}
我们使用 ::
语法访问 print_config
函数。
下面是模块树的样子:
我们已经成功声明了 config
模块! 但这还不足以让我们在 config.rs
中调用 print_config
函数。Rust中几乎所有的东西默认都是私有的,我们需要使用pub关键字将该函数公开。
pub关键字使事物公开:
// main.rs
mod config;
fn main() {
config::print_config();
println!("main");
}
// config.rs
- fn print_config() {
+ pub fn print_config() {
println!("config");
}
现在,这起作用了。我们已经成功地调用了一个定义在不同文件中的函数!
例子2
让我们试着从 main.rs
中调用 routes/health_route.rs
中定义的 print_health_route
函数。
// main.rs
mod config;
fn main() {
config::print_config();
println!("main");
}
// routes/health_route.rs
fn print_health_route() {
println!("health_route");
}
正如我们前面讨论的,我们只能对同一目录下的 my_module.rs
或 my_module/mod.rs
使用 mod
关键字。
所以为了从 main.rs
调用 routes/health_route.rs
里面的函数,我们需要做以下事情:
-
创建一个名为
routes/mod.rs
的文件,在main.rs
中声明routes
子模块 -
在
routes/mod.rs
中声明health_route
子模块并使其公开 -
将
health_route.rs
中的函数公开
my_project
├── Cargo.toml
└─┬ src
├── main.rs
├── config.rs
├─┬ routes
+ │ ├── mod.rs
│ ├── health_route.rs
│ └── user_route.rs
└─┬ models
└── user_model.rs
// main.rs
mod config;
+ mod routes;
fn main() {
+ routes::health_route::print_health_route();
config::print_config();
println!("main");
}
// routes/mod.rs
+ pub mod health_route;
// routes/health_route.rs
- fn print_health_route() {
+ pub fn print_health_route() {
println!("health_route");
}
下面是模块树的样子:
我们现在可以调用一个文件夹内的文件中定义的函数。
例子3
让我们试试从 main.rs => routes/user_route.rs => models/user_model.rs
中调用:
// main.rs
mod config;
mod routes;
fn main() {
routes::health_route::print_health_route();
config::print_config();
println!("main");
}
// routes/user_route.rs
fn print_user_route() {
println!("user_route");
}
// models/user_model.rs
fn print_user_model() {
println!("user_model");
}
我们想从 main 的 print_user_route 中调用函数 print_user_model。
让我们做和以前一样的修改–声明子模块,使函数公开,并添加 mod.rs 文件。
my_project
├── Cargo.toml
└─┬ src
├── main.rs
├── config.rs
├─┬ routes
│ ├── mod.rs
│ ├── health_route.rs
│ └── user_route.rs
└─┬ models
+ ├── mod.rs
└── user_model.rs
// main.rs
mod config;
mod routes;
+ mod models;
fn main() {
routes::health_route::print_health_route();
+ routes::user_route::print_user_route();
config::print_config();
println!("main");
}
// routes/mod.rs
pub mod health_route;
+ pub mod user_route;
// routes/user_route.rs
- fn print_user_route() {
+ pub fn print_user_route() {
println!("user_route");
}
// models/mod.rs
+ pub mod user_model;
// models/user_model.rs
- fn print_user_model() {
+ pub fn print_user_model() {
println!("user_model");
}
下面是模块树的样子。
等等,我们实际上还没有从 print_user_route中
调用 print_user_model
! 到目前为止,我们只从 main.rs
中调用了其他模块中定义的函数,我们如何从其他文件中调用呢?
如果我们看一下我们的模块树,print_user_model
函数位于 crate::models::user_model
路径中。所以为了在非main.rs的文件中使用一个模块,我们应该从模块树中到达该模块的必要路径来考虑。
// routes/user_route.rs
pub fn print_user_route() {
+ crate::models::user_model::print_user_model();
println!("user_route");
}
我们已经成功地从一个不是 main.rs 的文件中调用了一个定义在文件中的函数。
super
如果我们的文件组织深入到多个目录,全称就会变得过于冗长。假设由于某种原因,我们想从 print_user_route
调用 print_health_route
。这些文件分别在 crate::routes::health_route
和 crate::routes::user_route
的路径下。
我们可以使用全称 crate::routes::health_route::print_health_route()
来调用它,但我们也可以使用相对路径super::health_route::print_health_route()
;。注意,我们用super来指代父级范围。
模块路径中的super关键字指的是父作用域
pub fn print_user_route() {
crate::routes::health_route::print_health_route();
// can also be called using
super::health_route::print_health_route();
println!("user_route");
}
use
在上面的例子中,如果使用完全合格的名称,甚至是相对名称,都是很繁琐的。为了缩短名称,我们可以使用 use 关键字,将路径绑定到一个新的名称或别名上。
use关键字用于缩短模块的路径
pub fn print_user_route() {
crate::models::user_model::print_user_model();
println!("user_route");
}
上述代码可以重构为:
use crate::models::user_model::print_user_model;
pub fn print_user_route() {
print_user_model();
println!("user_route");
}
我们可以不使用 print_user_model
这个名字,而是把它别名为其他东西:
use crate::models::user_model::print_user_model as log_user_model;
pub fn print_user_route() {
log_user_model();
println!("user_route");
}
外部模块
添加到 Cargo.toml
中的依赖关系对项目中的所有模块都是可用的。我们不需要明确地导入或声明任何东西来使用一个依赖关系。
外部依赖对项目中的所有模块都是全局可用的。
例如,假设我们将 rand
crate添加到我们的项目中。我们可以在我们的代码中直接使用它,例如:
pub fn print_health_route() {
let random_number: u8 = rand::random();
println!("{}", random_number);
println!("health_route");
}
我们也可以用使用来缩短路径:
use rand::random;
pub fn print_health_route() {
let random_number: u8 = random();
println!("{}", random_number);
println!("health_route");
}
摘要
-
模块系统是明确的–与文件系统没有1:1的映射关系
-
我们在一个文件的父级中声明模块,而不是文件本身
-
mod 关键字用于声明子模块
-
我们需要明确地将函数、结构体等声明为 public 的,这样它们就可以被其他模块所使用
-
pub关键字使事物公开
-
use关键字用于缩短模块的路径
-
我们不需要明确声明第三方模块
4 - Rust Lints
4.1 - Rust Lints概述
在软件中,“Lints”是用于帮助改善源代码的工具。Rust编译器包含许多Lints,并且在编译代码时,它还将运行Lints。这些Lints可能会产生警告,错误或根本不产生任何东西,具体取决于您配置事物的方式。
这是一个小例子:
$ cat main.rs
fn main() {
let x = 5;
}
$ rustc main.rs
warning: unused variable: `x`
--> main.rs:2:9
|
2 | let x = 5;
| ^
|
= note: `#[warn(unused_variables)]` on by default
= note: to avoid this warning, consider using `_x` instead
这是unused_variables
Lints,它告诉您已引入了代码中的变量未使用。这不是bug,所以它不是一个错误,但它可能是一个错误,所以你得到一个警告。
4.2 - Lints等级
在 rustc
中,Lints分为四个级别:
- 允许/allow
- 警告/warn
- 拒绝/deny
- 禁止/forbid
每个Lint都有一个默认级别(在本章后面的Lint列表中有解释),编译器有一个默认警告级别。首先,让我们解释这些级别的含义,然后再讨论配置。
允许/allow
这些Lints存在,但默认情况下不执行任何操作。例如,考虑以下源代码:
pub fn foo() {}
编译此文件不会产生警告:
$ rustc lib.rs --crate-type=lib
$
但是此代码违反了missing_docs
lints。
这些Lint主要是通过配置手动打开的,我们将在本节后面讨论。
警告/warn
如果违反lint,“警告/warn” Lints等级将产生警告。例如,此代码违反了unused_variable
lint:
pub fn foo() {
let x = 5;
}
这将产生以下警告:
$ rustc lib.rs --crate-type=lib
warning: unused variable: `x`
--> lib.rs:2:9
|
2 | let x = 5;
| ^
|
= note: `#[warn(unused_variables)]` on by default
= note: to avoid this warning, consider using `_x` instead
拒绝/deny
如果违反,“拒绝” lint 将产生错误。例如,此代码导致了 exceeding_bitshifts
lint。
fn main() {
100u8 << 10;
}
$ rustc main.rs
error: bitshift exceeds the type's number of bits
--> main.rs:2:13
|
2 | 100u8 << 10;
| ^^^^^^^^^^^
|
= note: `#[deny(exceeding_bitshifts)]` on by default
Lint错误和常规的旧错误有什么区别?Lint可通过级别进行配置,因此,与“允许/allow” Lint相似,默认情况下设置为“拒绝/deny”,则警告/warn 让您允许它们。同样,您可能希望设置一个Lints,warn
默认情况下会产生错误。此Lint等级可为您提供。
禁止/forbid
“禁止/forbid”是一种特殊的Lint级别,比“拒绝/deny”级别高。与“拒绝/deny”相同的是,在此级别的Lint将产生错误,但是与“拒绝/deny”级别不同,“禁止/forbid”级别不能被覆盖为低于错误的任何值。但是,Lint的水平可能仍会受到限制--cap-lints
(请参见下文),因此rustc --cap-lints warn
将使Lint设置为“禁止/forbid”只是警告。
配置警告级别
还记得我们missing_docs
从“允许/allow”Lint级别开始的示例吗?
$ cat lib.rs
pub fn foo() {}
$ rustc lib.rs --crate-type=lib
$
我们可以使用编译器标志以及源代码中的属性来配置该Lint以更高级别运行。
您还可以“限制”Lint,以便编译器可以选择忽略某些Lint级别。我们将在最后讨论。
通过编译器标志
在-A
,-W
,-D
,和-F
标志让你把一个或多个Lint设置成允许,警告,拒绝或禁止的等级,就像这样:
$ rustc lib.rs --crate-type=lib -W missing-docs
warning: missing documentation for crate
--> lib.rs:1:1
|
1 | pub fn foo() {}
| ^^^^^^^^^^^^
|
= note: requested on the command line with `-W missing-docs`
warning: missing documentation for a function
--> lib.rs:1:1
|
1 | pub fn foo() {}
| ^^^^^^^^^^^^
$ rustc lib.rs --crate-type=lib -D missing-docs
error: missing documentation for crate
--> lib.rs:1:1
|
1 | pub fn foo() {}
| ^^^^^^^^^^^^
|
= note: requested on the command line with `-D missing-docs`
error: missing documentation for a function
--> lib.rs:1:1
|
1 | pub fn foo() {}
| ^^^^^^^^^^^^
error: aborting due to 2 previous errors
您还可以多次传递每个标志,以更改多个Lint:
$ rustc lib.rs --crate-type=lib -D missing-docs -D unused-variables
当然,您可以将这四个标志混合在一起:
$ rustc lib.rs --crate-type=lib -D missing-docs -A unused-variables
通过属性
还可以使用crate范围的属性修改Lint级别:
$ cat lib.rs
#![warn(missing_docs)]
pub fn foo() {}
$ rustc lib.rs --crate-type=lib
warning: missing documentation for crate
--> lib.rs:1:1
|
1 | / #![warn(missing_docs)]
2 | |
3 | | pub fn foo() {}
| |_______________^
|
note: lint level defined here
--> lib.rs:1:9
|
1 | #![warn(missing_docs)]
| ^^^^^^^^^^^^
warning: missing documentation for a function
--> lib.rs:3:1
|
3 | pub fn foo() {}
| ^^^^^^^^^^^^
所有四种等级,warn
,allow
,deny
,和forbid
,都可以这样工作。
您还可以为每个属性传递多个Lint:
#![warn(missing_docs, unused_variables)]
pub fn foo() {}
并一起使用多个属性:
#![warn(missing_docs)]
#![deny(unused_variables)]
pub fn foo() {}
封顶Lint
rustc
支持 --cap-lints LEVEL
的标志来设置“Lint上限” 。这是所有Lint的最高等级。因此,例如,如果我们从上面的“ deny” lint级别获取代码示例:
fn main() {
100u8 << 10;
}
然后我们对其进行编译,并为Lint封顶警告:
$ rustc lib.rs --cap-lints warn
warning: bitshift exceeds the type's number of bits
--> lib.rs:2:5
|
2 | 100u8 << 10;
| ^^^^^^^^^^^
|
= note: `#[warn(exceeding_bitshifts)]` on by default
warning: this expression will panic at run-time
--> lib.rs:2:5
|
2 | 100u8 << 10;
| ^^^^^^^^^^^ attempt to shift left with overflow
现在仅警告,而不是错误。我们可以走得更远,并允许所有Lint:
$ rustc lib.rs --cap-lints allow
$
Cargo大量使用此特性;它会使用 --cap-lints allow
以便在编译依赖项时通过,这样,即使它们有任何警告,也不会污染生成的输出。
4.3 - Lints分组
备注:翻译自英文原文: https://doc.rust-lang.org/rustc/lints/groups.html
rustc
具有“Lint Group”的概念,您可以通过一个名称切换多个警告。
例如,nonstandard-style
lint 一次性设置 non-camel-case-types
, non-snake-case
以及non-upper-case-globals
。所以一下是等效的:
$ rustc -D nonstandard-style
$ rustc -D non-camel-case-types -D non-snake-case -D non-upper-case-globals
以下是每个Lint组及其组成的Lint的列表:
组 | 描述 | 棉绒 |
---|---|---|
nonstandard-style | 违反标准命名约定 | 非驼峰式,非蛇形,非大写全局 non-camel-case-types, non-snake-case, non-upper-case-globals |
warnings | 发出警告的所有Lint | 发出警告的所有Lint |
2018版 | 在Rust 2018中将变成错误的Lint | tyvar-behind-raw-pointer |
rust-2018-idioms | 推动您朝Rust 2018的惯例前进的Lint | bare-trait-object, unreachable-pub |
unused | 检测到已声明但未使用的事物的Lint | unused-imports, unused-variables, unused-assignments, dead-code, unused-mut, unreachable-code, unreachable-patterns, unused-must-use, unused-unsafe, path-statements, unused-attributes, unused-macros, unused-allocation, unused-doc-comment, unused-extern-crates, unused-features, unused-parens |
future-incompatible | 检测到代码具有未来兼容性问题的Lint | private-in-public, pub-use-of-private-extern-crate, patterns-in-fns-without-body, safe-extern-statics, invalid-type-param-default, legacy-directory-ownership, legacy-imports, legacy-constructor-visibility, missing-fragment-specifier, illegal-floating-point-literal-pattern, anonymous-parameters, parenthesized-params-in-types-and-modules, late-bound-lifetime-arguments, safe-packed-borrows, tyvar-behind-raw-pointer, unstable-name-collision |
此外,还有一个bad-style
Lint Group,它是 nonstandard-style
的别名,但已经不推荐使用。
最后,您还可以通过调用来查看上表rustc -W help
。这将为您已安装的特定编译器提供确切值。
4.4 - 默认为Allow的Lints
英文原文地址: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html
默认情况下,这些 lint 都设置为 allow 级别。因此,除非您使用标志或属性将它们设置为更高的 lint 级别,否则它们将不会显示。
anonymous-parameters
此 lint 检测匿名参数。一些触发此 lint 的示例代码:
trait Foo {
fn foo(usize);
}
当设置为’deny’时,这将产生:
error: use of deprecated anonymous parameter
--> src/lib.rs:5:11
|
5 | fn foo(usize);
| ^
|
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #41686 <https://github.com/rust-lang/rust/issues/41686>
这种语法大多是历史原因,可以很容易地解决:
trait Foo {
fn foo(_: usize);
}
bare-trait-object
这个 lint 暗示对 trait 对象,使用dyn Trait
。一些触发此 lint 的示例代码:
#![feature(dyn_trait)]
trait Trait { }
fn takes_trait_object(_: Box<Trait>) {
}
当设置为’deny’时,这将产生:
error: trait objects without an explicit `dyn` are deprecated
--> src/lib.rs:7:30
|
7 | fn takes_trait_object(_: Box<Trait>) {
| ^^^^^ help: use `dyn`: `dyn Trait`
|
要解决此问题,请按照帮助消息的建议执行操作:
#![feature(dyn_trait)]
#![deny(bare_trait_objects)]
trait Trait { }
fn takes_trait_object(_: Box<dyn Trait>) {
}
box-pointers
给 Box 类型使用的 lints。一些触发此 lint 的示例代码:
struct Foo {
x: Box<isize>,
}
当设置为’deny’时,这将产生:
error: type uses owned (Box type) pointers: std::boxed::Box<isize>
--> src/lib.rs:6:5
|
6 | x: Box<isize> //~ ERROR type uses owned
| ^^^^^^^^^^^^^
|
这种 lint 主要是历史性的,并不是特别有用。以前,Box
是用于构建语言,以及进行堆分配的唯一方法。今天的 Rust 可以调用其他分配器等。
elided-lifetime-in-path
此 lint 检测隐藏生命周期参数的使用。一些触发此 lint 的示例代码:
struct Foo<'a> {
x: &'a u32
}
fn foo(x: &Foo) {
}
当设置为’deny’时,这将产生:
error: hidden lifetime parameters are deprecated, try `Foo<'_>`
--> src/lib.rs:5:12
|
5 | fn foo(x: &Foo) {
| ^^^
|
生命周期省略规则隐藏这个生命周期,但是这个被弃用了。
missing-copy-implementations
这个 lint 检测到可能被遗忘的Copy
实现。一些触发此 lint 的示例代码:
pub struct Foo {
pub field: i32
}
当设置为’deny’时,这将产生:
error: type could implement `Copy`; consider adding `impl Copy`
--> src/main.rs:3:1
|
3 | / pub struct Foo { //~ ERROR type could implement `Copy`; consider adding `impl Copy`
4 | | pub field: i32
5 | | }
| |_^
|
您可以通过派生Copy
,来修复 lint。
这个 lint 被设置为’allow’,因为这个代码并不坏; 特别是常写一个类似这样的新类型,所以一个Copy
类型不再是Copy
(it’s common to write newtypes like this specifically so that a Copy
type is no longer Copy
)。
missing-debug-implementations
此 lint 检测到缺少的fmt::Debug
实现。一些触发此 lint 的示例代码:
pub struct Foo;
当设置为’deny’时,这将产生:
error: type does not implement `fmt::Debug`; consider adding #[derive(Debug)] or a manual implementation
--> src/main.rs:3:1
|
3 | pub struct Foo;
| ^^^^^^^^^^^^^^^
|
您可以通过派生Debug
来修复 lint。
missing-docs
此 lint 检测到公有项的缺乏文档。一些触发此 lint 的示例代码:
pub fn foo() {}
当设置为’deny’时,这将产生:
error: missing documentation for crate
--> src/main.rs:1:1
|
1 | / #![deny(missing_docs)]
2 | |
3 | | pub fn foo() {}
4 | |
5 | | fn main() {}
| |____________^
|
error: missing documentation for a function
--> src/main.rs:3:1
|
3 | pub fn foo() {}
| ^^^^^^^^^^^^
要修复 lint,请为所有项添加文档。
single-use-lifetime
此 lint 检测仅使用一次的生命周期。一些触发此 lint 的示例代码:
struct Foo<'x> {
x: &'x u32
}
当设置为’deny’时,这将产生:
error: lifetime name `'x` only used once
--> src/main.rs:3:12
|
3 | struct Foo<'x> {
| ^^
|
trivial-casts
这种 lint 可以检测到可以移除的琐碎成本。一些触发此 lint 的示例代码:
let x: &u32 = &42;
let _ = x as *const u32;
当设置为’deny’时,这将产生:
error: trivial cast: `&u32` as `*const u32`. Cast can be replaced by coercion, this might require type ascription or a temporary variable
--> src/main.rs:5:13
|
5 | let _ = x as *const u32;
| ^^^^^^^^^^^^^^^
|
note: lint level defined here
--> src/main.rs:1:9
|
1 | #![deny(trivial_casts)]
| ^^^^^^^^^^^^^
trivial-numeric-casts
此 lint 检测可以删除的数字类型的简单转换。一些触发此 lint 的示例代码:
let x = 42i32 as i32;
当设置为’deny’时,这将产生:
error: trivial numeric cast: `i32` as `i32`. Cast can be replaced by coercion, this might require type ascription or a temporary variable
--> src/main.rs:4:13
|
4 | let x = 42i32 as i32;
| ^^^^^^^^^^^^
|
unreachable-pub
无法从crate root到达的pub
项会出发这个lint。一些触发此 lint 的示例代码:
mod foo {
pub mod bar {
}
}
当设置为’deny’时,这将产生:
error: unreachable `pub` item
--> src/main.rs:4:5
|
4 | pub mod bar {
| ---^^^^^^^^
| |
| help: consider restricting its visibility: `pub(crate)`
|
unsafe-code
这种 lint 可以使用unsafe
码。一些触发此 lint 的示例代码:
fn main() {
unsafe {
}
}
当设置为’deny’时,这将产生:
error: usage of an `unsafe` block
--> src/main.rs:4:5
|
4 | / unsafe {
5 | |
6 | | }
| |_____^
|
unstable-features
此 lint 已弃用,不再使用。
unused-extern-crates
这种 lint 可以检测标记为 extern crate
但是从未使用过的情况。一些触发此 lint 的示例代码:
extern crate semver;
当设置为’deny’时,这将产生:
error: unused extern crate
--> src/main.rs:3:1
|
3 | extern crate semver;
| ^^^^^^^^^^^^^^^^^^^^
|
unused-import-braces
此 lint 捕获导入项目周围,有不必要的括号。一些触发此 lint 的示例代码:
use test::{A};
pub mod test {
pub struct A;
}
当设置为’deny’时,这将产生:
error: braces around A is unnecessary
--> src/main.rs:3:1
|
3 | use test::{A};
| ^^^^^^^^^^^^^^
|
要解决这个问题,use test::A;
unused-qualifications
此 lint 检测到不必要的限定名称。一些触发此 lint 的示例代码:
mod foo {
pub fn bar() {}
}
fn main() {
use foo::bar;
foo::bar();
}
当设置为’deny’时,这将产生:
error: unnecessary qualification
--> src/main.rs:9:5
|
9 | foo::bar();
| ^^^^^^^^
|
你可以直接调用bar()
,没有foo::
。
unused-results
此 lint 检查语句中表达式的未使用结果。一些触发此 lint 的示例代码:
fn foo<T>() -> T { panic!() }
fn main() {
foo::<usize>();
}
当设置为’deny’时,这将产生:
error: unused result
--> src/main.rs:6:5
|
6 | foo::<usize>();
| ^^^^^^^^^^^^^^^
|
variant-size-differences
此 lint 检测具有各种变量大小的枚举。一些触发此 lint 的示例代码:
enum En {
V0(u8),
VBig([u8; 1024]),
}
当设置为’deny’时,这将产生:
error: enum variant is more than three times larger (1024 bytes) than the next largest
--> src/main.rs:5:5
|
5 | VBig([u8; 1024]), //~ ERROR variant is more than three times larger
| ^^^^^^^^^^^^^^^^
|
4.5 - 默认为Warn的Lints
英文原文地址: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html
默认情况下,这些 lint 都设置为“警告”级别。
const-err
该 lint 在进行持续求值时,检测到一个错误的表达。一些触发此 lint 的示例代码:
let b = 200u8 + 200u8;
这将产生:
warning: attempt to add with overflow
--> src/main.rs:2:9
|
2 | let b = 200u8 + 200u8;
| ^^^^^^^^^^^^^
|
dead-code
此 lint 检测未使用的,未导出的项。一些触发此 lint 的示例代码:
fn foo() {}
这将产生:
warning: function is never used: `foo`
--> src/lib.rs:2:1
|
2 | fn foo() {}
| ^^^^^^^^
|
deprecated
此 lint 检测使用已弃用的项目。一些触发此 lint 的示例代码:
#[deprecated]
fn foo() {}
fn bar() {
foo();
}
这将产生:
warning: use of deprecated item 'foo'
--> src/lib.rs:7:5
|
7 | foo();
| ^^^
|
illegal-floating-point-literal-pattern
此 lint 检测模式中使用的浮点数字面量。一些触发此 lint 的示例代码:
let x = 42.0;
match x {
5.0 => {},
_ => {},
}
这将产生:
warning: floating-point literals cannot be used in patterns
--> src/main.rs:4:9
|
4 | 5.0 => {},
| ^^^
|
= note: #[warn(illegal_floating_point_literal_pattern)] on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #41620 <https://github.com/rust-lang/rust/issues/41620>
improper-ctypes
此 lint 检测到外部模块中, libc 类型的正确使用。一些触发此 lint 的示例代码:
extern "C" {
static STATIC: String;
}
这将产生:
warning: found struct without foreign-function-safe representation annotation in foreign module, consider adding a #[repr(C)] attribute to the type
--> src/main.rs:2:20
|
2 | static STATIC: String;
| ^^^^^^
|
late-bound-lifetime-arguments
此 lint 使用后绑定生命周期参数,检测路径片段中的泛型生存周期参数。一些触发此 lint 的示例代码:
struct S;
impl S {
fn late<'a, 'b>(self, _: &'a u8, _: &'b u8) {}
}
fn main() {
S.late::<'static>(&0, &0);
}
这将产生:
warning: cannot specify lifetime arguments explicitly if late bound lifetime parameters are present
--> src/main.rs:8:14
|
4 | fn late<'a, 'b>(self, _: &'a u8, _: &'b u8) {}
| -- the late bound lifetime parameter is introduced here
...
8 | S.late::<'static>(&0, &0);
| ^^^^^^^
|
= note: #[warn(late_bound_lifetime_arguments)] on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #42868 <https://github.com/rust-lang/rust/issues/42868>
non-camel-case-types
此 lint 检测没有驼峰形式名称的类型,变体,trait 和类型参数。一些触发此 lint 的示例代码:
struct s;
这将产生:
warning: type `s` should have a camel case name such as `S`
--> src/main.rs:1:1
|
1 | struct s;
| ^^^^^^^^^
|
non-shorthand-field-patterns
此 lint 检测在一个模式中,使用Struct { x: x }
能代替Struct { x }
。一些触发此 lint 的示例代码:
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point {
x: 5,
y: 5,
};
match p {
Point { x: x, y: y } => (),
}
}
这将产生:
warning: the `x:` in this pattern is redundant
--> src/main.rs:14:17
|
14 | Point { x: x, y: y } => (),
| --^^
| |
| help: remove this
|
warning: the `y:` in this pattern is redundant
--> src/main.rs:14:23
|
14 | Point { x: x, y: y } => (),
| --^^
| |
| help: remove this
non-snake-case
此 lint 检测没有蛇形式名称的变量,方法,函数,生命周期参数和模块。一些触发此 lint 的示例代码:
let X = 5;
这将产生:
warning: variable `X` should have a snake case name such as `x`
--> src/main.rs:2:9
|
2 | let X = 5;
| ^
|
non-upper-case-globals
此 lint 检测不大写的静态常量。一些触发此 lint 的示例代码:
static x: i32 = 5;
这将产生:
warning: static variable `x` should have an upper case name such as `X`
--> src/main.rs:1:1
|
1 | static x: i32 = 5;
| ^^^^^^^^^^^^^^^^^^
|
no-mangle-generic-items
此 lint 检测泛型项必须被修复(mangle)。一些触发此 lint 的示例代码:
#[no_mangle]
fn foo<T>(t: T) {
}
这将产生:
warning: functions generic over types must be mangled
--> src/main.rs:2:1
|
1 | #[no_mangle]
| ------------ help: remove this attribute
2 | / fn foo<T>(t: T) {
3 | |
4 | | }
| |_^
|
path-statements
此 lint 检测路径语句无效。一些触发此 lint 的示例代码:
let x = 42;
x;
这将产生:
warning: path statement with no effect
--> src/main.rs:3:5
|
3 | x;
| ^^
|
patterns-in-fns-without-body
这个 lint 检测到以前的错误,就是没有body的函数模式。一些触发此 lint 的示例代码:
trait Trait {
fn foo(mut arg: u8);
}
这将产生:
warning: patterns aren't allowed in methods without bodies
--> src/main.rs:2:12
|
2 | fn foo(mut arg: u8);
| ^^^^^^^
|
= note: #[warn(patterns_in_fns_without_body)] on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #35203 <https://github.com/rust-lang/rust/issues/35203>
要解决此问题,请删除该模式; 它可以在实现中使用,而无需在定义中使用mut
。那是:
trait Trait {
fn foo(arg: u8);
}
impl Trait for i32 {
fn foo(mut arg: u8) {
}
}
plugin-as-library
此 lint 检测,当编译器插件用作非插件包中的普通库。一些触发此 lint 的示例代码:
#![feature(plugin)]
#![plugin(macro_crate_test)]
extern crate macro_crate_test;
private-in-public
此 lint 检测未被旧实现捕获的公有接口中的私有项。一些触发此 lint 的示例代码:
pub trait Trait {
type A;
}
pub struct S;
mod foo {
struct Z;
impl ::Trait for ::S {
type A = Z;
}
}
这将产生:
error[E0446]: private type `foo::Z` in public interface
--> src/main.rs:11:9
|
11 | type A = Z;
| ^^^^^^^^^^^ can't leak private type
private-no-mangle-fns
此 lint 检测标记#[no_mangle]
的函数,这也是私有的。鉴于私有函数不公开,并且#[no_mangle]
控制公有符号,这种组合是错误的。一些触发此 lint 的示例代码:
#[no_mangle]
fn foo() {}
这将产生:
warning: function is marked #[no_mangle], but not exported
--> src/main.rs:2:1
|
2 | fn foo() {}
| -^^^^^^^^^^
| |
| help: try making it public: `pub`
|
要解决此问题,请将其公有或删除#[no_mangle]
。
private-no-mangle-statics
此 lint 检测到标记#[no_mangle]
的任何静态是私有的。鉴于私有静态不公开,并且#[no_mangle]
控制公共符号,这种组合是错误的。一些触发此 lint 的示例代码:
#[no_mangle]
static X: i32 = 4;
这将产生:
warning: static is marked #[no_mangle], but not exported
--> src/main.rs:2:1
|
2 | static X: i32 = 4;
| -^^^^^^^^^^^^^^^^^
| |
| help: try making it public: `pub`
|
要解决此问题,请将其公开或删除#[no_mangle]
。
renamed-and-removed-lints
此 lint 检测已重命名或删除的 lint。一些触发此 lint 的示例代码:
#![deny(raw_pointer_derive)]
这将产生:
warning: lint raw_pointer_derive has been removed: using derive with raw pointers is ok
--> src/main.rs:1:9
|
1 | #![deny(raw_pointer_derive)]
| ^^^^^^^^^^^^^^^^^^
|
要解决此问题,请删除 lint 或使用新名称。
safe-packed-borrows
此 lint 检测借用除 1 之外的压缩对齐结构内部的字段。触发此 lint 的一些示例代码:
#[repr(packed)]
pub struct Unaligned<T>(pub T);
pub struct Foo {
start: u8,
data: Unaligned<u32>,
}
fn main() {
let x = Foo { start: 0, data: Unaligned(1) };
let y = &x.data.0;
}
这将产生:
warning: borrow of packed field requires unsafe function or block (error E0133)
--> src/main.rs:11:13
|
11 | let y = &x.data.0;
| ^^^^^^^^^
|
= note: #[warn(safe_packed_borrows)] on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #46043 <https://github.com/rust-lang/rust/issues/46043>
stable-features
此 lint 检测#[feature]
的属性变得稳定了。一些触发此 lint 的示例代码:这将产生:
#![feature(test_accepted_feature)]
要修复,只需删除
warning: this feature has been stable since 1.0.0. Attribute no longer needed
--> src/main.rs:1:12
|
1 | #![feature(test_accepted_feature)]
| ^^^^^^^^^^^^^^^^^^^^^
|
属性,因为它不再需要。#![feature]
类型的别名边界
type-alias-bounds
这 lint 检测类型别名的边界。目前未被执行。一些触发此 lint 的示例代码:
type SendVec<T: Send> = Vec<T>;
这将产生:
warning: type alias is never used: `SendVec`
--> src/main.rs:1:1
|
1 | type SendVec<T: Send> = Vec<T>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
tyvar-behind-raw-pointer
此 lint 检测指向一个推断变量的原始指针。一些触发此 lint 的示例代码:
let data = std::ptr::null();
let _ = &data as *const *const ();
if data.is_null() {}
这将产生:
warning: type annotations needed
--> src/main.rs:4:13
|
4 | if data.is_null() {}
| ^^^^^^^
|
= note: #[warn(tyvar_behind_raw_pointer)] on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in the 2018 edition!
= note: for more information, see issue #46906 <https://github.com/rust-lang/rust/issues/46906>
unconditional-recursion
此 lint 检测在不调用自身的情况下,无法返回的函数。一些触发此 lint 的示例代码:
fn foo() {
foo();
}
这将产生:
warning: function cannot return without recursing
--> src/main.rs:1:1
|
1 | fn foo() {
| ^^^^^^^^ cannot return without recursing
2 | foo();
| ----- recursive call site
|
unions-with-drop-fields
此 lint 检测联合的使用,其包含可能是重要丢弃代码的字段。一些触发此 lint 的示例代码:
#![feature(untagged_unions)]
union U {
s: String,
}
这将产生:
warning: union contains a field with possibly non-trivial drop code, drop code of union fields is ignored when dropping the union
--> src/main.rs:4:5
|
4 | s: String,
| ^^^^^^^^^
|
unknown-lints
此 lint 检测无法识别的 lint 属性。一些触发此 lint 的示例代码:
#[allow(not_a_real_lint)]
这将产生:
warning: unknown lint: `not_a_real_lint`
--> src/main.rs:1:10
|
1 | #![allow(not_a_real_lint)]
| ^^^^^^^^^^^^^^^
|
unreachable-code
此 lint 检测无法访问的代码路径。一些触发此 lint 的示例代码:
panic!("we never go past here!");
let x = 5;
这将产生:
warning: unreachable statement
--> src/main.rs:4:5
|
4 | let x = 5;
| ^^^^^^^^^^
|
unreachable-patterns
此 lint 检测到无法访问的模式。一些触发此 lint 的示例代码:
let x = 5;
match x {
y => (),
5 => (),
}
这将产生:
warning: unreachable pattern
--> src/main.rs:5:5
|
5 | 5 => (),
| ^
|
该y
模式永远匹配,所以五个是不可能达到的。记住,匹配武器按顺序匹配,你可能想把它5
上面的案例y
案件。
此 lint 检测到您使用了标准库计划在将来添加的名称,这意味着将来如果没有其他类型注释,您的代码可能无法编译。请重命名,或立即添加这些注释。
unused-allocation
此 lint 检测可以消除的不必要的内存分配。
unused-assignments
此 lint 检测永远不会读取的内存分配。一些触发此 lint 的示例代码:
let mut x = 5;
x = 6;
这将产生:
warning: value assigned to `x` is never read
--> src/main.rs:4:5
|
4 | x = 6;
| ^
|
unused-attributes
此 lint 检测编译器未使用的属性。一些触发此 lint 的示例代码:
#![feature(custom_attribute)]
#![mutable_doc]
这将产生:
warning: unused attribute
--> src/main.rs:4:1
|
4 | #![mutable_doc]
| ^^^^^^^^^^^^^^^
|
unused-comparisons
此 lint 检测到所涉及的类型限制使得 比较操作 变得无用。一些触发此 lint 的示例代码:
fn foo(x: u8) {
x >= 0;
}
这将产生:
warning: comparison is useless due to type limits
--> src/main.rs:6:5
|
6 | x >= 0;
| ^^^^^^
|
unused-doc-comment
此 lint 检测 rustdoc 未使用的 doc 注释。一些触发此 lint 的示例代码:
/// docs for x
let x = 12;
这将产生:
warning: doc comment not used by rustdoc
--> src/main.rs:2:5
|
2 | /// docs for x
| ^^^^^^^^^^^^^^
|
unused-features
此 lint 检测在 crate-level 的#[feature]
指令 中,找到的未使用或未知功能。要解决此问题,只需删除功能标志即可。
unused-imports
此 lint 检测从未使用过的导入。一些触发此 lint 的示例代码:
use std::collections::HashMap;
这将产生:
warning: unused import: `std::collections::HashMap`
--> src/main.rs:1:5
|
1 | use std::collections::HashMap;
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
unused-macros
此 lint 检测未使用的宏。一些触发此 lint 的示例代码:
macro_rules! unused {
() => {};
}
fn main() {
}
这将产生:
warning: unused macro definition
--> src/main.rs:1:1
|
1 | / macro_rules! unused {
2 | | () => {};
3 | | }
| |_^
|
unused-must-use
此 lint 检测标记为#[must_use]
的类型的未使用结果。一些触发此 lint 的示例代码:
fn returns_result() -> Result<(), ()> {
Ok(())
}
fn main() {
returns_result();
}
这将产生:
warning: unused `std::result::Result` that must be used
--> src/main.rs:6:5
|
6 | returns_result();
| ^^^^^^^^^^^^^^^^^
|
unused-mut
此 lint 检测不需要可变的 mut 变量。一些触发此 lint 的示例代码:
let mut x = 5;
这将产生:
warning: variable does not need to be mutable
--> src/main.rs:2:9
|
2 | let mut x = 5;
| ----^
| |
| help: remove this `mut`
|
unused-parens
这个Lint检测到if
,match
,while
和return
的括号; 这个括号是不需要的。触发此 lint 的示例代码:
if(true) {}
这将产生:
warning: unnecessary parentheses around `if` condition
--> src/main.rs:2:7
|
2 | if(true) {}
| ^^^^^^ help: remove these parentheses
|
unused-unsafe
这个 lint 检测到不必要的使用unsafe
块。一些触发此 lint 的示例代码:
unsafe {}
这将产生:
warning: unnecessary `unsafe` block
--> src/main.rs:2:5
|
2 | unsafe {}
| ^^^^^^ unnecessary `unsafe` block
|
unused-variables
此 lint 检测未以任何方式使用的变量。触发此 lint 的示例代码:
let x = 5;
这将产生:
warning: unused variable: `x`
--> src/main.rs:2:9
|
2 | let x = 5;
| ^ help: consider using `_x` instead
|
warnings
这种 lint 有点特别;通过更改其级别,您可以更改每个其他警告,这些警告会对您想要的任何值产生警告:
#![deny(warnings)]
因此,您不会直接在代码中触发此 lint。
while-true
这个 lint 检测到while true { }
。一些触发此 lint 的示例代码:
while true {
}
这将产生:
warning: denote infinite loops with `loop { ... }`
--> src/main.rs:2:5
|
2 | while true {
| ^^^^^^^^^^ help: use `loop`
|
4.6 - 默认为Deny的Lints
英文原文地址: https://doc.rust-lang.org/rustc/lints/listing/deny-by-default.html
默认情况下,这些 lint 都设置为’deny’级别。
exceeding-bitshifts
此 lint 检测到移位超出了类型的位数。一些触发此 lint 的示例代码:
1_i32 << 32;
这将产生:
error: bitshift exceeds the type's number of bits
--> src/main.rs:2:5
|
2 | 1_i32 << 32;
| ^^^^^^^^^^^
|
invalid-type-param-default
此 lint 检测在无效位置中,允许的类型参数默认值错误。一些触发此 lint 的示例代码:
fn foo<T=i32>(t: T) {}
这将产生:
error: defaults for type parameters are only allowed in `struct`, `enum`, `type`, or `trait` definitions.
--> src/main.rs:4:8
|
4 | fn foo<T=i32>(t: T) {}
| ^
|
= note: #[deny(invalid_type_param_default)] on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #36887 <https://github.com/rust-lang/rust/issues/36887>
legacy-constructor-visibility
RFC 1506修改了一些可见性规则,并改变了 struct 构造函数的可见性。一些触发此 lint 的示例代码:
mod m {
pub struct S(u8);
fn f() {
// this is trying to use S from the 'use' line, but because the `u8` is
// not pub, it is private
::S;
}
}
use m::S;
这将产生:
error: private struct constructors are not usable through re-exports in outer modules
--> src/main.rs:5:9
|
5 | ::S;
| ^^^
|
= note: #[deny(legacy_constructor_visibility)] on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #39207 <https://github.com/rust-lang/rust/issues/39207>
legacy-directory-ownership
发出 legacy_directory_ownership
时发出
- 有一个带有
#[path]
属性的非内联模块(例如#[path = "foo.rs"]
mod bar;), - 模块的文件(上例中的“foo.rs”)是未命名为“mod.rs”,并且
- 模块的文件包含一个
#[path]
属性的非内联模块。
可以通过将父模块重命名为“mod.rs”,并将其移动到其自己的目录(如果合适的话)来修复警告。
当一个未使用的macro_rules!
宏定义模式出现时,会发出 missing_fragment_specifier 警告,因其有一个元变量(例如$e
)后面没有片段说明符(例如:expr
)。
通过删除未使用的macro_rules!
宏定义模式,可以始终修复此警告。
mutable-transmutes
这种 lint 抓取&T
到&mut T
的转化,因为它是未定义的行为。一些触发此 lint 的示例代码:
unsafe {
let y = std::mem::transmute::<&i32, &mut i32>(&5);
}
这将产生:
error: mutating transmuted &mut T from &T may cause undefined behavior, consider instead using an UnsafeCell
--> src/main.rs:3:17
|
3 | let y = std::mem::transmute::<&i32, &mut i32>(&5);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
no-mangle-const-items
这个 lint 检测到任何带#[no_mangle]
属性的const
项。常量确实没有导出符号,因此,这可能意味着您打算使用static
不是const
。一些触发此 lint 的示例代码:
#[no_mangle]
const FOO: i32 = 5;
这将产生:
error: const items should never be #[no_mangle]
--> src/main.rs:3:1
|
3 | const FOO: i32 = 5;
| -----^^^^^^^^^^^^^^
| |
| help: try a static value: `pub static`
|
overflowing-literals
此 lint 检测其类型的字面值超出范围。一些触发此 lint 的示例代码:
let x: u8 = 1000;
这将产生:
error: literal out of range for u8
--> src/main.rs:2:17
|
2 | let x: u8 = 1000;
| ^^^^
|
parenthesized-params-in-types-and-modules
此 lint 检测到不正确的括号。一些触发此 lint 的示例代码:
let x = 5 as usize();
这将产生:
error: parenthesized parameters may only be used with a trait
--> src/main.rs:2:21
|
2 | let x = 5 as usize();
| ^^
|
= note: #[deny(parenthesized_params_in_types_and_modules)] on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #42238 <https://github.com/rust-lang/rust/issues/42238>
要修复它,请删除多个()
。
pub-use-of-private-extern-crate
此 lint 检测重新导出一个私有extern crate
的特定情况;
在旧版本的 Rust 中,允许extern static
以安全代码访问,会存在安全问题。这个 lint 现在抓住并否认这种代码。
此 lint 检测到在一个#[crate_type]
指示中,发现一个未知箱类型。一些触发此 lint 的示例代码:
#![crate_type="lol"]
这将产生:
error: invalid `crate_type` value
--> src/lib.rs:1:1
|
1 | #![crate_type="lol"]
| ^^^^^^^^^^^^^^^^^^^^
|
incoherent-fundamental-impls
此 lint 检测到错误允许的潜在冲突的 impl。一些触发此 lint 的示例代码:
pub trait Trait1<X> {
type Output;
}
pub trait Trait2<X> {}
pub struct A;
impl<X, T> Trait1<X> for T where T: Trait2<X> {
type Output = ();
}
impl<X> Trait1<Box<X>> for A {
type Output = i32;
}
这将产生:
error: conflicting implementations of trait `Trait1<std::boxed::Box<_>>` for type `A`: (E0119)
--> src/main.rs:13:1
|
9 | impl<X, T> Trait1<X> for T where T: Trait2<X> {
| --------------------------------------------- first implementation here
...
13 | impl<X> Trait1<Box<X>> for A {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `A`
|
= note: #[deny(incoherent_fundamental_impls)] on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #46205 <https://github.com/rust-lang/rust/issues/46205>
5 - Rustup
5.1 - Rustup概述
介绍
rustup 是系统编程语言Rust的安装程序。
rustup 从官方发布渠道安装Rust编程语言,使你能够在稳定版、测试版和nightly编译器之间轻松切换并保持更新。它使交叉编译变得更加简单,为普通平台的标准库建立二进制。而且它可以在Rust支持的所有平台上运行。
相关信息
-
rustup book: 对rustup有详细介绍
常用命令
安装rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
更新rust
rustup update
卸载rust
rustup self uninstall
打开本地文档
rust安装时会在本地生成一份离线文档,可以用下面的命令在浏览器中打开:
rustup doc
安装目标平台的标准库
rustup target
命令安装目标平台的标准库
rustup target add arm-linux-androideabi
镜像安装
5.2 - Rustup Book学习笔记
https://rust-lang.github.io/rustup/index.html
1. 安装
遵循rust安装指导
https://www.rust-lang.org/tools/install
rustup 将 rustc, cargo, rustup 和其他标准工具安装到 Cargo 的 bin 目录中。在Unix系统中,它位于
$HOME/.cargo/bin
,在Windows系统中位于%USERPROFILE%\.cargo/bin
。这个目录也是cargo install
安装Rust程序和Cargo插件的目录。
➜ bin pwd
/home/sky/.cargo/bin
➜ bin ls
cargo cargo-fmt clippy-driver rustc rustfmt rust-lldb
cargo-clippy cargo-miri rls rustdoc rust-gdb rustup
选择安装路径
rustup 允许在运行 rustup-init 可执行文件之前,通过设置环境变量 CARGO_HOME 和 RUSTUP_HOME 来定制安装。正如在环境变量部分提到的,RUSTUP_HOME 设置了 rustup 的根文件夹,用于存储已安装的工具链和配置选项。CARGO_HOME 包含 cargo 使用的缓存文件。
注意,需要确保这些环境变量始终被设置,并且在使用工具链时,
CARGO_HOME/bin
在 $PATH 环境变量中。
安装 nightly
暂时没有这个需求,跳过
开启完成功能
为Bash、Fish、Zsh或PowerShell启用tab完成功能
# Zsh
$ mkdir ~/.zfunc/
$ rustup completions zsh > ~/.zfunc/_rustup
加了这个之后 tab 会有自动完成。
2. 概念
rustup是一个工具链复用器(toolchain multiplexer)。它安装和管理许多Rust工具链,并通过一套安装在 ~/.cargo/bin
的工具来展示它们。安装在 ~/.cargo/bin
中的 rustc 和 cargo 可执行文件是委托给真正的工具链的代理。
因此,当 rustup 第一次安装时,运行 rustc 会运行 $HOME/.cargo/bin/rustc 中的代理,而它又会运行稳定的编译器。如果你后来用 rustup default nightly 把默认的工具链改成了nightly,那么同样的代理将运行nightly编译器。
这与Ruby的rbenv、Python的yenv或Node的nvm类似。
术语
-
channel - Rust被发布到三个不同的 “频道” : stable, beta, 和 nightly。
-
toolchain - “工具链” 是Rust编译器(rustc)和相关工具(如cargo)的完整安装。一个工具链的规范包括发布渠道或版本,以及工具链运行的主机平台。
-
target - rustc能够为许多平台生成代码。“target” 指定了代码将被生成的平台。默认情况下,cargo 和 rustc 使用主机工具链的平台作为目标。要为不同的目标平台进行编译,通常需要先通过
rustup target
命令安装目标平台的标准库。 -
component - 每个Rust版本都包括几个"组件",其中一些是必须的(比如rustc),一些是可选的(比如clippy)。
-
profile - 为了使组件的工作更容易,“profile"定义了组件的分组。
2.1 Channels
Rust被发布到三个不同的"频道”:stable, beta 和 nightly。稳定版每6周发布一次(偶尔会有点状发布)。测试版是将出现在下一个稳定版中的版本。nightly 发布是每晚进行的。rustup 协助安装不同的通道,保持它们的最新状态,并在它们之间轻松切换。
在安装了一个发布通道后,可以用 rustup 将安装的版本更新到该通道的最新版本。
rustup 也可以安装特定版本的Rust,比如 1.45.2 或 nightly-2020-07-27。
2.2 Toolchains
rustup的许多命令都涉及到工具链,工具链即Rust编译器的单一安装。最基本的是跟踪官方发布渠道:stable、beta和nightly;但rustup也可以从官方档案中安装工具链,用于替代的主机平台,以及从本地构建中安装。
工具链规范
标准发布通道的工具链名称有以下形式:
<channel>[-<date>][-<host>]
<channel> = stable|beta|nightly|<major.minor>|<major.minor.patch>
<date> = YYYY-MM-DD
<host> = <target-triple>
‘channel’ 是一个命名的发布通道,一个主要和次要的版本号如1.42,或一个完全指定的版本号如1.42.0。频道名称可以选择附加一个存档日期,如nightly-2014-12-18,在这种情况下,工具链会从该日期的存档中下载。
最后,host可以被指定为目标三元组 (target-triple) 。这对于在64位平台上安装32位编译器或在Windows上安装基于MSVC的工具链最为有用。比如说
$ rustup toolchain install stable-x86_64-pc-windows-msvc
为了方便起见,目标三元组中被省略的元素将被推断出来,所以上面的内容可以写成:
$ rustup toolchain install stable-msvc
2.3 Components
每个工具链都有几个"组件",其中一些是必须的(比如rustc),一些是可选的(比如clippy)。rustup component
命令是用来管理已安装的组件的。例如,运行 rustup component list
可以看到可用和已安装的组件的列表。
在安装工具链时,可以用 --component
标志来添加组件。比如:
rustup toolchain install nightly --component rust-docs
组件可以通过 ·rustup component· 命令被添加到已经安装的工具链中:
rustup component add rust-docs
为了更容易选择安装哪些组件,rustup有 “profile” 的概念,它提供了不同组件的命名分组。
大多数组件都有一个 target-triple 后缀,比如 rustc-x86_64-apple-darwin
,以表示该组件所适用的平台。
可用的组件集可能随不同的版本和工具链而变化。下面是对不同组件的概述:
- rustc - Rust编译器。
- cargo - Cargo是一个软件包管理器和构建工具。
- rustfmt - Rustfmt是一个用于自动格式化代码的工具。
- rust-std - 这是Rust的标准库。rustc支持的每个目标都有一个单独的rust-std组件,例如rust-std-x86_64-pc-windows-msvc。
- rust-docs - 这是一个Rust文档的本地副本。使用 rustup doc 命令可以在网络浏览器中打开文档。
- rls - RLS是一个语言服务器,提供对编辑器和IDE的支持。
- clippy - Clippy是一个lint工具,为常见的错误和风格选择提供额外的检查。
- miri - Miri是一个实验性的Rust解释器,它可以用来检查未定义行为。
- rust-src - 这是一个Rust标准库源代码的本地拷贝。它可以被一些工具使用,比如RLS,为标准库中的函数提供自动补全;Miri是一个Rust解释器;以及Cargo的实验性build-std功能,它允许你在本地重建标准库。
- rust-analysis - 关于标准库的元数据,由RLS等工具使用。
- rust-mingw - 这包含了一个链接器和平台库,用于在x86_64-pc-windows-gnu平台上构建。
- llvm-tools-preview - 这是一个实验性组件,包含LLVM工具的集合。
- rustc-dev - 这个组件包含作为库的编译器。大多数用户不需要这个;只有在开发链接到编译器的工具时才需要它,例如对Clippy进行修改。
2.4 Profiles
rustup 有一个 “配置文件” 的概念。它们是在安装新的Rust工具链时可以选择下载的组件组。目前可用的配置文件有minimal
, default
和 complete
:
-
minimal: 最小的配置文件包括尽可能少的组件,以获得一个可工作的编译器(rustc、rust-std和cargo)。如果你不使用本地文档,建议在Windows系统上使用这个组件(大量的文件可能会对一些反病毒系统造成问题),并在CI中使用。
-
default: 默认配置文件包括最小配置文件中的所有组件,并增加了 rust-docs、rustfmt 和 clippy。rustup默认使用这个配置文件,它是推荐用于一般用途的配置文件。
-
complete: 完整的配置文件包括所有通过 rustup 可用的组件。千万不要使用这个,因为它包括了元数据中曾经包含的所有组件,因此几乎总是会失败。如果你正在寻找一种方法来安装devtools,如miri或IDE集成工具(rls),你应该使用默认的配置文件,并手动安装所需的额外组件,可以使用
rustup component add
或在安装工具链时使用-c。
要改变rustup的配置文件,你可以使用rustup set profile命令。例如,要选择最小的配置文件,你可以使用。
rustup set profile minimal
也可以在第一次安装rustup时选择配置文件,可以通过选择 “Customize installation” 选项进行交互式操作,也可以通过传递 --profile=<name>
标志进行编程。配置文件只影响新安装的工具链:像往常一样,以后可以用:rustup component add
来安装单个组件。
2.5 Proxies
rustup为常见的Rust工具提供了一些包装器。这些被称为代理,代表了由各种组件提供的命令。
代理列表目前在rustup中是静态的,如下所示。
-
rustc是Rust编程语言的编译器,由项目本身提供,来自rustc组件。
-
rustdoc是发布在rustc组件中的工具,帮助你为Rust项目生成文档。
-
cargo是Rust包管理器,它下载Rust包的依赖项,编译你的包,制作可分发的包,并将它们上传到crates.io(Rust社区的包注册中心)。它来自cargo组件。
-
rust-lldb 和 rust-gdb 分别是 lldb 和 gdb 调试器的简单包装器。这些包装器可以实现一些Rust值的漂亮打印,并通过其脚本接口为调试器添加一些便利的功能。
-
rls是Rust IDE整合工具的一部分。它实现了语言服务器协议,允许IDE和编辑器,如Visual Studio Code、ViM或Emacs,访问你正在编辑的Rust代码的语义。它来自rls组件。
-
cargo-clippy和clippy-driver与clippy linting工具有关,它为常见的错误和风格选择提供额外的检查,它来自clippy组件。
-
cargo-miri是Rust的中级中间表示法(MIR)的实验性解释器,来自miri组件。
3. 基本使用
保持Rust的最新状态
Rust在三个不同的发布渠道上发布:stable、beta和nightly。rustup默认使用stable渠道,它代表Rust的最新版本。稳定版每六周发布一次新版本。
当新版本的Rust发布时,只需输入rustup update即可更新。
4. 覆盖
rustup 在执行已安装的命令(如rustc)时,会自动决定使用哪个工具链。有几种方法可以控制和覆盖哪个工具链被使用:
- 在命令行中使用的工具链覆盖简写,如
cargo +beta
。 RUSTUP_TOOLCHAIN
环境变量。- 目录覆盖,用
rustup override
命令设置。 - rust-toolchain.toml 文件。
- 默认的工具链。
工具链的选择是按照上面列出的顺序,使用第一个被指定的工具链。不过有一个例外:目录覆盖和rust-toolchain.toml文件也是按照它们与当前目录的接近程度来选择。也就是说,这两种覆盖方法是通过向文件系统根部的目录树上走来发现的,离当前目录较近的rust-toolchain.toml文件将比离当前目录较远的目录覆盖更受欢迎。
要验证哪个工具链是活动的,可以使用 rustup show
.
这是 rustup show
在linux上执行的结果:
rustup show
Default host: x86_64-unknown-linux-gnu
rustup home: /home/sky/.rustup
stable-x86_64-unknown-linux-gnu (default)
rustc 1.55.0 (c8dfcfe04 2021-09-06)
TODO: 有需要时再细看怎么覆盖吧。
5. 交叉编译
Rust支持大量的平台。对于其中的许多平台,Rust项目发布了标准库的二进制版本,而对于一些平台则发布了完整的编译器。
当你第一次安装工具链时,rustup只安装你的主机平台的标准库,也就是你目前运行的架构和操作系统。要编译到其他平台,你必须安装其他目标平台。这可以通过 rustup target add
命令完成。例如,要添加Android target。
$ rustup target add arm-linux-androideabi
info: downloading component 'rust-std' for 'arm-linux-androideabi'
info: installing component 'rust-std' for 'arm-linux-androideabi'
安装了 arm-linux-androideabi
目标后,你就可以通过 --target
标志用 Cargo 构建 Android,如 cargo build --target=arm-linux-androideabi
。
注意,rustup target add
只安装指定目标的Rust标准库。通常,交叉编译还需要其他工具,特别是链接器。例如,要交叉编译到Android,必须安装Android NDK。在未来,rustup也会提供安装NDK组件的帮助。
要为一个不是默认工具链的工具链安装一个目标,可以使用 rustup target add
的 --toolchain
参数,像这样:
$ rustup target add --toolchain <toolchain> <target>...
要查看可用目标的列表,可以使用 rustup target list
。要删除以前添加的目标,请使用 rustup target remove
。
感受一下可用目标的数量:
$ rustup target list
aarch64-apple-darwin
aarch64-apple-ios
aarch64-fuchsia
aarch64-linux-android
aarch64-pc-windows-msvc
aarch64-unknown-linux-gnu
aarch64-unknown-linux-musl
aarch64-unknown-none
aarch64-unknown-none-softfloat
arm-linux-androideabi
arm-unknown-linux-gnueabi
arm-unknown-linux-gnueabihf
arm-unknown-linux-musleabi
arm-unknown-linux-musleabihf
armebv7r-none-eabi
armebv7r-none-eabihf
armv5te-unknown-linux-gnueabi
armv5te-unknown-linux-musleabi
armv7-linux-androideabi
armv7-unknown-linux-gnueabi
armv7-unknown-linux-gnueabihf
armv7-unknown-linux-musleabi
armv7-unknown-linux-musleabihf
armv7a-none-eabi
armv7r-none-eabi
armv7r-none-eabihf
asmjs-unknown-emscripten
i586-pc-windows-msvc
i586-unknown-linux-gnu
i586-unknown-linux-musl
i686-linux-android
i686-pc-windows-gnu
i686-pc-windows-msvc
i686-unknown-freebsd
i686-unknown-linux-gnu
i686-unknown-linux-musl
mips-unknown-linux-gnu
mips-unknown-linux-musl
mips64-unknown-linux-gnuabi64
mips64-unknown-linux-muslabi64
mips64el-unknown-linux-gnuabi64
mips64el-unknown-linux-muslabi64
mipsel-unknown-linux-gnu
mipsel-unknown-linux-musl
nvptx64-nvidia-cuda
powerpc-unknown-linux-gnu
powerpc64-unknown-linux-gnu
powerpc64le-unknown-linux-gnu
riscv32i-unknown-none-elf
riscv32imac-unknown-none-elf
riscv32imc-unknown-none-elf
riscv64gc-unknown-linux-gnu
riscv64gc-unknown-none-elf
riscv64imac-unknown-none-elf
s390x-unknown-linux-gnu
sparc64-unknown-linux-gnu
sparcv9-sun-solaris
thumbv6m-none-eabi
thumbv7em-none-eabi
thumbv7em-none-eabihf
thumbv7m-none-eabi
thumbv7neon-linux-androideabi
thumbv7neon-unknown-linux-gnueabihf
thumbv8m.base-none-eabi
thumbv8m.main-none-eabi
thumbv8m.main-none-eabihf
wasm32-unknown-emscripten
wasm32-unknown-unknown
wasm32-wasi
x86_64-apple-darwin
x86_64-apple-ios
x86_64-fortanix-unknown-sgx
x86_64-fuchsia
x86_64-linux-android
x86_64-pc-solaris
x86_64-pc-windows-gnu
x86_64-pc-windows-msvc
x86_64-sun-solaris
x86_64-unknown-freebsd
x86_64-unknown-illumos
x86_64-unknown-linux-gnu (installed)
x86_64-unknown-linux-gnux32
x86_64-unknown-linux-musl
x86_64-unknown-netbsd
x86_64-unknown-redox
6. 环境变量
备注:临时查吧
7. 配置
Rustup有一个TOML设置文件,位于 ${RUSTUP_HOME}/settings.toml
(默认为 ~/.rustup
或 %USERPROFILE%/.rustup
)。这个文件的模式不是rustup公共接口的一部分–应该使用rustup CLI来查询和设置设置。
在Unix操作系统上,一些设置会参考一个后备设置文件。这个回退文件位于 /etc/rustup/settings.toml
,目前只能定义 default_toolchain
。
8. 网络代理
企业网络通常没有直接的外部 HTTP 访问,而是强制使用代理。如果你在这样的网络中,你可以通过在环境中设置 rustup 的URL来要求它使用代理。在大多数情况下,设置 https_proxy 应该就足够了。不同的系统和shell之间的命令可能不同。
-
在一个使用bash或zsh等shell的类Unix系统上
export https_proxy=socks5://proxy.example.com:1080
-
在Windows命令提示符(cmd)上
set https_proxy=socks5://proxy.example.com:1080
-
在Windows PowerShell(或PowerShell Core)上
$env:https_proxy="socks5://proxy.example.com:1080"
-
当使用HTTP代理时,将
socks5://proxy.example.com:1080
替换为http://proxy.example.com:8080
。
9. 案例
Command | Description |
---|---|
rustup default nightly |
Set the default toolchain to the latest nightly |
rustup set profile minimal |
Set the default profile |
rustup target list |
List all available targets for the active toolchain |
rustup target add arm-linux-androideabi |
Install the Android target |
rustup target remove arm-linux-androideabi |
Remove the Android target |
rustup run nightly rustc foo.rs |
Run the nightly regardless of the active toolchain |
rustc +nightly foo.rs |
Shorthand way to run a nightly compiler |
rustup run nightly bash |
Run a shell configured for the nightly compiler |
rustup default stable-msvc |
On Windows, use the MSVC toolchain instead of GNU |
rustup override set nightly-2015-04-01 |
For the current directory, use a nightly from a specific date |
rustup toolchain link my-toolchain "C:\RustInstallation" |
Install a custom toolchain by symlinking an existing installation |
rustup show |
Show which toolchain will be used in the current directory |
rustup toolchain uninstall nightly |
Uninstall a given toolchain |
rustup toolchain help |
Show the help page for a subcommand (like toolchain ) |
rustup man cargo |
(Unix only) View the man page for a given command (like cargo ) |