这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

Rust学习笔记

1 - 介绍

Rust的介绍,以及Rust的资料收集

1.1 - Rust版本

Rust版本

Edition Guide 官方英文版本:

https://doc.rust-lang.org/edition-guide/

中文翻译版本:

https://erasin.wang/books/edition-guide-cn/

补充说明:信息量很大。

1.2 - 资料收集

收集Rust的各种资料

官方网站

社区

文档

Rust中文网站有一个Rust官方书籍的汇总页面:

https://rustlang-cn.org/office/rust/

书籍

rust中文社区有个资源收集项目,里面有不少好书:

https://github.com/rustlang-cn/resourses/tree/master/books

The Rust Programming Language

官方书籍"The Rust Programming Language", 有第一版第二版。其中第一版已经过时,第二版几乎是重写。这本书网上有电子版可以下载

“The Rust Programming Language"中文翻译版本, 第一版翻译第二版翻译

是目前非常好的的Rust入门书籍。

Rust异步编程

翻译自自官方async-book

Rust by Example

这本书的内容非常不错,强烈推荐给入门读者。就是内容稍微老了点。

资源

Awesome Rust

https://github.com/rust-unofficial/awesome-rust

最齐全的Rust资料列表

Rust中文社区的 Awesome Rust:

https://rustlang-cn.org/resourse/awesome/

值得新手关注的Rust项目

适用于新手学习rust的项目有:

资源介绍文章

2 - Rust安装

在各个操作系统上安装Rust

Rust安装方式参考官方地址: https://www.rust-lang.org/en-US/install.html

2.1 - Linux&Mac安装

在Linux和Mac上安装Rust

安装

执行命令:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

然后依照屏幕提示:

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
info: downloading installer

Welcome to Rust!

This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.

Rustup metadata and toolchains will be installed into the Rustup
home directory, located at:

  /home/sky/.rustup

This can be modified with the RUSTUP_HOME environment variable.

The Cargo home directory located at:

  /home/sky/.cargo

This can be modified with the CARGO_HOME environment variable.

The cargo, rustc, rustup and other commands will be added to
Cargo's bin directory, located at:

  /home/sky/.cargo/bin

This path will then be added to your PATH environment variable by
modifying the profile files located at:

  /home/sky/.profile
  /home/sky/.bashrc
  /home/sky/.zshenv

You can uninstall at any time with rustup self uninstall and
these changes will be reverted.

Current installation options:


   default host triple: x86_64-unknown-linux-gnu
     default toolchain: stable (default)
               profile: default
  modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
>1

这里选择1默认模式,继续:

info: profile set to 'default'
info: default host triple is x86_64-unknown-linux-gnu
info: syncing channel updates for 'stable-x86_64-unknown-linux-gnu'
info: latest update on 2021-07-29, rust version 1.54.0 (a178d0322 2021-07-26)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-docs'
 16.7 MiB /  16.7 MiB (100 %)   4.5 MiB/s in  3s ETA:  0s
info: downloading component 'rust-std'
 21.9 MiB /  21.9 MiB (100 %)   6.0 MiB/s in  4s ETA:  0s
info: downloading component 'rustc'
 50.1 MiB /  50.1 MiB (100 %)   8.7 MiB/s in  6s ETA:  0s
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
 16.7 MiB /  16.7 MiB (100 %)  12.6 MiB/s in  1s ETA:  0s
info: installing component 'rust-std'
 21.9 MiB /  21.9 MiB (100 %)  16.0 MiB/s in  1s ETA:  0s
info: installing component 'rustc'
 50.1 MiB /  50.1 MiB (100 %)  17.8 MiB/s in  2s ETA:  0s
info: installing component 'rustfmt'
info: default toolchain set to 'stable-x86_64-unknown-linux-gnu'

  stable-x86_64-unknown-linux-gnu installed - rustc 1.54.0 (a178d0322 2021-07-26)


Rust is installed now. Great!

To get started you may need to restart your current shell.
This would reload your PATH environment variable to include
Cargo's bin directory ($HOME/.cargo/bin).

To configure your current shell, run:
source $HOME/.cargo/env

如果不想重新登录而立即生效,只需要在终端执行命令 source $HOME/.cargo/env 即可。

验证安装:

rustc --version
rustc 1.54.0 (a178d0322 2021-07-26)

配置cargo

打开(或创建)文件 ~/.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"

参考:

2.2 - Windows安装

在Windows上安装Rust

安装vs2015

TBD: 下次验证,参考 https://rustlang-cn.org/office/rust/book/getting-started/ch01-01-installation.html

在安装rust之前,windows平台上需要先安装 Microsoft C++ build tools,推荐2015版本。如果不安装,后面在编译时,会报错说"link.exe"无法找到。

note: the msvc targets depend on the msvc linker but `link.exe` was not found

note: please ensure that VS 2013 or VS 2015 was installed with the Visual C++ option

error: aborting due to previous error

error: Could not compile `ws2_32-sys`

打开下面的网站:

http://landinghub.visualstudio.com/visual-cpp-build-tools

选择"Download Visual C++ Build Tools 2015"。

下载之后按照提示一路安装即可。

windows安装rust

操作系统为windows 10 64位。

下载rustup.init.exe,然后安装,按照指示操作,中间要下载rustc等安装文件。

Rust Visual C++ prerequisites

If you will be targeting the GNU ABI or otherwise know what you are doing then
it is fine to continue installation without the build tools, but otherwise,
install the C++ build tools before proceeding.

Continue? (Y/n) y


Welcome to Rust!

This will download and install the official compiler for the Rust programming
language, and its package manager, Cargo.

It will add the cargo, rustc, rustup and other commands to Cargo's bin
directory, located at:

  C:\Users\aoxia\.cargo\bin

This path will then be added to your PATH environment variable by modifying the
HKEY_CURRENT_USER/Environment/PATH registry key.

You can uninstall at any time with rustup self uninstall and these changes will
be reverted.

Current installation options:

   default host triple: x86_64-pc-windows-msvc
     default toolchain: stable
  modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation


info: updating existing rustup installation


Rust is installed now. Great!

To get started you need Cargo's bin directory (%USERPROFILE%\.cargo\bin) in
your PATH environment variable. Future applications will automatically have the
correct environment, but you may need to restart your current shell.

Press the Enter key to continue.

%USERPROFILE%\.cargo\bin 加入到PATH环境变量中。

验证安装,执行rustc --version

$> rustc --version
rustc 1.33.0 (2aa4c46cf 2019-02-28) 

2.3 - Rust版本升级

升级Rust版本

官方方式

通过 rustup 安装了 Rust 之后,更新到最新版本只要运行如下更新脚本:

$ rustup update

尝试在mac下从 1.40 升级到 1.42 成功,输出如下:

$ rustup update
info: syncing channel updates for 'stable-x86_64-apple-darwin'
463.3 KiB / 463.3 KiB (100 %) 414.2 KiB/s in  1s ETA:  0s
info: latest update on 2020-03-12, rust version 1.42.0 (b8cedc004 2020-03-09)
info: downloading component 'rust-src'
  2.2 MiB /   2.2 MiB (100 %) 313.6 KiB/s in  8s ETA:  0s
info: downloading component 'cargo'
  3.7 MiB /   3.7 MiB (100 %) 321.3 KiB/s in 14s ETA:  0s
info: downloading component 'clippy'
  1.3 MiB /   1.3 MiB (100 %) 327.7 KiB/s in  5s ETA:  0s
info: downloading component 'rust-docs'
 12.1 MiB /  12.1 MiB (100 %) 323.2 KiB/s in 43s ETA:  0s
info: downloading component 'rust-std'
 16.1 MiB /  16.1 MiB (100 %) 345.6 KiB/s in  1m  3s ETA:  0s
info: downloading component 'rustc'
 54.5 MiB /  54.5 MiB (100 %) 342.4 KiB/s in  4m 20s ETA:  0s    
info: downloading component 'rustfmt'
  1.9 MiB /   1.9 MiB (100 %) 288.0 KiB/s in  7s ETA:  0s
info: removing previous version of component 'rust-src'
info: removing previous version of component 'cargo'
info: removing previous version of component 'clippy'
info: removing previous version of component 'rust-docs'
info: removing previous version of component 'rust-std'
info: removing previous version of component 'rustc'
info: removing previous version of component 'rustfmt'
info: installing component 'rust-src'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
 12.1 MiB /  12.1 MiB (100 %)   8.6 MiB/s in  1s ETA:  0s
info: installing component 'rust-std'
info: installing component 'rustc'
 54.5 MiB /  54.5 MiB (100 %)  18.3 MiB/s in  3s ETA:  0s
info: installing component 'rustfmt'
info: checking for self-updates

  stable-x86_64-apple-darwin updated - rustc 1.42.0 (b8cedc004 2020-03-09) (from rustc 1.40.0 (73528e339 2019-12-16))

info: cleaning up downloads & tmp directories

尝试在linux 下从 1.54.0 升级到 1.55.0 成功,输出如下:

➜  ~ rustup update
info: syncing channel updates for 'stable-x86_64-unknown-linux-gnu'

679.5 KiB / 679.5 KiB (100 %) 463.5 KiB/s in  1s ETA:  0s
info: latest update on 2021-09-09, rust version 1.55.0 (c8dfcfe04 2021-09-06)
info: downloading component 'rust-src'
info: downloading component 'cargo'
  6.1 MiB /   6.1 MiB (100 %)   4.4 MiB/s in  1s ETA:  0s
info: downloading component 'clippy'
info: downloading component 'rust-docs'
 17.0 MiB /  17.0 MiB (100 %)   4.5 MiB/s in  3s ETA:  0s
info: downloading component 'rust-std'
 22.3 MiB /  22.3 MiB (100 %)   4.6 MiB/s in  5s ETA:  0s
info: downloading component 'rustc'
 51.0 MiB /  51.0 MiB (100 %)   4.2 MiB/s in 11s ETA:  0s
info: downloading component 'rustfmt'
info: removing previous version of component 'rust-src'
info: removing previous version of component 'cargo'
info: removing previous version of component 'clippy'
info: removing previous version of component 'rust-docs'
info: removing previous version of component 'rust-std'
info: removing previous version of component 'rustc'
info: removing previous version of component 'rustfmt'
info: installing component 'rust-src'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
 17.0 MiB /  17.0 MiB (100 %)  11.3 MiB/s in  1s ETA:  0s
info: installing component 'rust-std'
 22.3 MiB /  22.3 MiB (100 %)  16.7 MiB/s in  1s ETA:  0s
info: installing component 'rustc'
 51.0 MiB /  51.0 MiB (100 %)  18.9 MiB/s in  2s ETA:  0s
info: installing component 'rustfmt'
info: checking for self-updates

  stable-x86_64-unknown-linux-gnu updated - rustc 1.55.0 (c8dfcfe04 2021-09-06) (from rustc 1.54.0 (a178d0322 2021-07-26))

info: cleaning up downloads & tmp directories

如果要卸载 Rust 和 rustup,运行如下卸载脚本:

$ rustup self uninstall

归档内容:曾经遇到的升级问题

尝试过升级已经安装的rust/cargo,试图从1.22.1升级到1.23,发现重新运行rustup脚本,虽然报告说安装成功,但是实际不会安装新的版本。

暂时没有找到升级的方法,只好用最笨的办法,先删除再全新安装:

cd
rm -rf .cargo/ .rustup/

这个方式理所当然的很不好,原有的所有内容都要重头来一次。

后面似乎没有再遇到类似问题。

2.4 - Clion设置

Clion安装和设置

下载

https://www.jetbrains.com/clion/download/

安装

安装clion

解压缩安装文件,按照 Install-Linux-tar.txt 的提示,执行

cd /bib
./clion.sh

启动clion界面之后,在左下角点 “Options Menu” 按钮,点 “Create Desktop Entry…” 创建启动菜单。

安装rust和toml插件

在 clion 的 “settings” -> “plugin” 中,安装 Rust 和 toml 两个插件。重启 clion。

配置

安装Rust标准库

进入 “settings” -> “language & framework” 下找到 “rust”,在 “Standard library” 那里选择 “Download via Rustup” 下载。

备注:如果下载速度慢,建议科学上网。

在CLion中Debug

详细介绍参考:Debugging - Rust | JetBrains

2.5 - Intellij Rust使用

Intellij Rust的使用技巧

背景

intellij-rust 网站:

https://intellij-rust.github.io/

格式化代码

Ctrl+Alt+L

TBD:这个方式格式化的代码,和 rustfmt 是否一致?待验证

选中代码

ctrl+w 扩大代码选择范围,可以连续使用

ctrl+shift+w 缩小代码选择范围,和 ctrl+w 扩大的顺序相反,可以连续使用

代码注释

选中代码之后(包括手工选择和 ctrl+w),Ctrl+Shift+/ 用块注释将代码注释为/* */Ctrl+/ 用行注释将代码注释为//

代码补全

Ctrl+Space 做代码补全,但是和输入法快捷键冲突。

Alt+/ 是 “dumb completion”

代码导航

Alt+F7 在caret中找到使用

Ctrl+B goto declaration

Ctrl+N goto class

Ctrl+Shift+Alt+N goto symbol,查找任何symbol(types, methods, functions, fields),配合 Ctrl+N 使用

Ctrl+U goto super

Ctrl+F12 在编辑页面弹出窗口显示当前文件的文件结构

Alt+7在打开一个导航栏显示当前文件的文件结构

Ctrl+Q 显示注释

Ctrl+Shift+P 显示表达式的类型

2.6 - Rust标准库中文文档

Rust标准库中文文档

https://github.com/wtklbm/rust-library-i18n

Rust 核心库和标准库的源码级中文翻译,可作为 IDE 工具的智能提示,也可以生成本地 API 文档

备注:更新到了 1.56.1 最新版本,真是不容易,鸣谢作者。

离线 HTML 文档

https://github.com/wtklbm/rust-library-i18n/blob/main/BuildHtml.md

参考上面的文档构建本地离线文档

备注:遇到一点小问题,提交了一个issue: https://github.com/wtklbm/rust-library-i18n/issues/7

3 - Rust构建

Rust 构建

3.1 - Rustc

Rustc

介绍

rustc是 Rust 编程语言的编译器,由项目组开发提供。编译器将您的源代码和生产二进制代码,变成一个或可执行文件。

大多数 Rust 程序员都不会直接调用rustc,而是通过Cargo来完成,虽然Cargo也是调用rustc流程!如果想看看 Cargo 如何调用rustc, 可以

$ cargo build --verbose

它会打印出每个rustc调用。

资料

  • The rustc book: 官方英文版
  • The rustc book: 中文翻译版。备注:经常打不开,需要科学上网。另外lint相关的内容和英文文档的内容有很大的偏差。

3.1.1 - Rustc的命令行参数

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。工件与--emitCLI参数中的请求相对应,并且该工件在文件系统上可用时,将立即发出通知。

请注意,它是无效的结合--json论点与--color 论据,并且需要结合起来--json使用--error-format=json

有关更多详细信息,请参见JSON章节

@path:从路径加载命令行标志

如果@path在命令行上指定,则它将打开path并从中读取命令行选项。这些选项是每行一个。空行表示空选项。该文件可以使用Unix或Windows样式的行尾,并且必须编码为UTF-8。

3.1.2 - Rustc的条件编译

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目标时进行,并同时设置unixwindows配置选项。实际执行此操作是不明智的。

#[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!");
}

这会在编译时被替换为一个truefalse,依配置设定而定。

参考资料

3.2 - Rust Cargo

Rust Cargo

3.2.1 - Cargo概述

Cargo概述

3.2.2 - Cargo设置

Cargo设置: Cargo Crates Registry Source设置和代理设置

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"

3.2.3 - Rust Cargo Book笔记

Rust Cargo Book笔记

https://doc.rust-lang.org/cargo/index.html

3.2.3.1 - Rust Cargo Book之指南

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.tomlCargo.lock 存放在包的根目录下(package root)。
  • 源代码放在 src 目录中。
  • 默认的库文件是 src/lib.rs
  • 默认的可执行文件是 src/main.rs
    • 其他可执行文件可以放在 src/bin/ 目录中。
  • 基准文件放在 benches 目录下。
  • 示例放在 examples 目录中。
  • 集成测试放在 tests 目录中。

如果binary、example、bench或集成测试由多个源文件组成,请将 main.rs 文件与额外的模块一起放在 src/binexamplesbenchestests 目录的子目录中。可执行文件的名称将是该目录的名称。

你可以在书中了解更多关于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 installrustup 安装的 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.crateregistry/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。

在配置文件目录(debugrelease)中,工件被放置在以下目录中。

目录 描述
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 packagecargo 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文档。

3.2.3.2 - Cargo Reference笔记: 依赖

Rust Cargo Reference笔记:3.1节依赖

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.00.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.y0.x.z 兼容,其中 y≥zx>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

比较要求

比较要求允许手动指定一个版本范围或一个确切的版本来依赖。

下面是一些比较要求的例子:

>= 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" }

指定来自 git 仓库的依赖关系

要依赖位于git仓库中的库,你需要指定的最小信息是带有git钥匙的仓库的位置:

[dependencies]
rand = { git = "https://github.com/rust-lang-nursery/rand" }

Cargo会在这个位置获取git仓库,然后在git仓库的任何地方寻找所要求的 crate 的 Cargo.toml(不一定在根部–例如,指定一个工作区的成员crate名称,并设置git为包含该工作区的仓库)。

由于我们没有指定任何其他信息,Cargo假定我们打算使用主分支上的最新提交来构建我们的包。你可以将 git key 与 revtagbranch 键结合起来,指定其他内容。下面是一个指定要使用 next 分支上的最新提交的例子:

[dependencies]
rand = { git = "https://github.com/rust-lang-nursery/rand", branch = "next" }

一旦添加了git依赖,Cargo会将该依赖锁定在当时的最新提交。一旦锁定,新的提交将不会被自动拉下。不过,它们可以通过 cargo update 手动拉下。

请参阅Git认证,以获得关于私有仓库的Git认证的帮助。

指定路径依赖

随着时间的推移,我们在指南中的 ·hello_world· 包的体积已经大大增加了。我们可能想把它拆成一个单独的 crate 供别人使用。为了做到这一点,Cargo支持路径依赖,通常是在一个仓库中的子程序。让我们先在 hello_world 包中建立一个新的crate:

# 在hello_world/的内部
$ cargo new hello_utils

这将创建一个新的文件夹 hello_utils,其中的 Cargo.tomlsrc 文件夹已经准备好被配置。为了告诉 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" }

多个位置

可以同时指定注册表版本和 gitpath 位置。gitpath 依赖将在本地使用(在这种情况下,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一样,这里的语法支持 notanyall 操作符,以组合各种 cfg name/value 对。

如果你想知道哪些 cfg 目标在你的平台上可用,在命令行中运行 rustc --print=cfg。如果你想知道哪些cfg目标在其他平台上可用,比如64位Windows,可以运行 rustc --print=cfg --target=x86_64-pc-windows-msvc

与你的 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"

构建依赖性

您可以在您的构建脚本中依赖其他基于Cargo的 crate 来使用。依赖关系通过清单中的 build-dependencies 部分来声明。

[build-dependencies]
cc = "1.0.3"

你也可以通过在目标部分标题中使用 build-dependencies 而不是 dependencies 来获得特定目标的构建依赖。比如说:

[target.'cfg(unix)'.build-dependencies]
cc = "1.0.3"

在这种情况下,只有当主机平台与指定的目标相匹配时,才会构建该依赖关系。

构建脚本不能访问在 dependenciesdev-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 仓库中提取。

3.2.3.3 - Cargo Reference笔记: 清单

Rust Cargo Reference笔记:3.2节清单

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 newcargo 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"

keywords字段

keywords 字段是一个描述此包的字符串数组。当在注册表上搜索该包时,这可以提供帮助,你可以选择任何可以帮助别人找到这个crate的词语。

[package]
# ...
keywords = ["gamedev", "graphics"]

categories字段

categories字段是一个字符串数组,表示该包属于的类别:

categories = ["command-line-utilities", "development-tools::cargo-plugins"]

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.rssrc/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。

配置目标

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 autobinsautoexamplesautotestsautobenches 设置为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

3.2.3.4 - Cargo Reference笔记: 工作区

Rust Cargo Reference笔记:3.3节工作区

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 中的数据,如果这对相关工具来说是有意义的。

3.2.3.5 - Cargo Reference笔记: 特性

Rust Cargo Reference笔记:3.4节特性

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开头,在第一个字符之后还可以包含-+.

默认特性

默认情况下,所有的特性都是禁用的,除非明确启用。这可以通过指定默认功能来改变。

[features
default = ["ico", "webp"]
bmp = []
png = []
ico = ["bmp", "png"]
webp = []

当软件包被构建时,default 特性被启用,这反过来又会启用所列出的特性。这种行为可以通过以下方式改变:

  • --no-default-features 命令行标志禁用软件包的默认特性。

  • 可以在依赖关系声明中指定 default-features = false 选项。

可选依赖关系

依赖关系可以被标记为"optional",这意味着它们将不会被默认编译。例如,假设我们的2D图像处理库使用一个外部包来处理GIF图像。这可以这样表达:

[dependencies

gif = { version = "0.11.1", optional = true }

可选依赖关系隐含地定义了一个与依赖关系同名的特性。这意味着可以在代码中使用相同的 cfg(feature = "gif") 语法,并且可以像启用 --features gif 这样的特性一样启用该依赖关系(见下面的命令行特性选项)。

明确定义的特性也可以启用可选的依赖关系。只要在特性列表中包括可选依赖的名称即可。例如,假设为了支持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"] }

依赖关系的特性也可以在 [features] 表中启用。语法是 “package-name/feature-name”。例如。

[dependencies
jpeg-decoder = { version = "0.1.20", default-features = false }

[features]
# 通过启用 jpeg-decoder 的 "rayon" 特性来启用并行处理支持。
parallel = ["jpeg-decoder/rayon"]

命令行特性选项

以下命令行标志可用于控制哪些特性被启用:

  • --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 将在启用所有这四个特性后被构建:

winapi-features

这样做的结果是,特性应该是累加的。也就是说,启用特性不应该使功能失效,而且通常启用任何特性的组合都是安全的。特性不应该引入 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.3 - Rust Module

Rust Module

3.3.1 - Learn Rust中的Module

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

superself 关键字可以在路径中使用,以消除访问项目时的歧义,并防止不必要的路径硬编码。

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.3.2 - Rust编程语言:使用package,crate和module来管理成长中的项目

Rust编程语言:使用package,crate和module来管理成长中的项目

3.3.2.1 - 模块系统

Rust编程语言:模块系统

https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html

编写大型程序时,组织代码将是很重要的,因为在头脑中保持对整个程序的跟踪将变得不可能。通过对相关功能进行分组,并将具有不同特征的代码分开,你将明确在哪里可以找到实现某个特定功能的代码,以及在哪里可以改变某个功能的工作方式。

到目前为止,我们所写的程序都是在单个文件的单个模块中。随着项目的发展,可以通过把代码分成多个模块,然后再分成多个文件来组织代码。一个 package 可以包含多个二进制 crate,也可以选择一个库 crate。随着 package 的增长,可以将部分内容提取到独立的 crate 中,成为外部依赖。本章涵盖了所有这些技术。对于由一组相互关联的 package 组成的、共同发展的大型项目,Cargo提供了工作空间,我们将在第14章的 “Cargo工作空间” 部分介绍。

除了对功能进行分组外,封装实现细节还可以让你在更高层次上重用代码:一旦你实现了某个操作,其他代码就可以通过代码的 public 接口调用该代码,而不需要知道实现是如何工作的。你写代码的方式定义了哪些部分是 public 的,供其他代码使用,哪些部分是 private 的实现细节,你保留改变的权利。这是另一种限制你必须记在脑子里的细节数量的方法。

scope 是一个相关的概念:编写代码的嵌套上下文有一组被定义为 “in scope” 的名称。在阅读、编写和编译代码时,程序员和编译器需要知道某个特定位置的特定名称是否指的是一个变量、函数、结构体、枚举、模块、常量或其他项目,以及该项目意味着什么。你可以创建作用域并改变哪些名字在作用域内或作用域外。你不能在同一个作用域中有两个同名的项目;有工具可以解决名称冲突。

Rust有很多功能可以让你管理代码组织,包括哪些细节是 public 的,哪些细节是 private 的,以及在程序中每个范围内有哪些名字。这些功能,有时被统称为模块系统,包括:

  • Package: Cargo 的一个功能,可以建立、测试和分享 crate。

  • Crate:一个模块树,产生一个库或可执行文件

  • Moduleuse: 让你控制路径的组织、范围和隐私

  • Path:命名项目的方式,如结构体、函数或模块

在本章中,我们将介绍所有这些功能,讨论它们如何相互作用,并解释如何使用它们来管理范围。到最后,你应该对模块系统有一个扎实的了解,并且能够像专家一样使用作用域。

3.3.2.2 - Package和Crate

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.rssrc/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.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),并在模块的主体周围加上大括号。在模块内部,我们可以有其他的模块,如本例中的 hostingserving 模块。模块还可以容纳其他项目的定义,如结构体、枚举、常量、特征,或如清单7-1中的函数。

通过使用模块,我们可以将相关的定义组合在一起,并说明它们为什么相关。使用这段代码的程序员会更容易找到他们想要使用的定义,因为他们可以根据分组来浏览代码,而不必阅读所有的定义。为这段代码添加新功能的程序员会知道该把代码放在哪里,以保持程序的条理性。

早些时候,我们提到 src/main.rssrc/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 里面)。这棵树还显示一些模块是彼此的兄弟姐妹,这意味着它们被定义在同一个模块中(hostingserving 被定义在 front_of_house 中)。为了继续这个家庭隐喻,如果模块A包含在模块B里面,我们就说模块A是模块B的孩子,模块B是模块A的父母。请注意,整个模块树的根在名为crate的隐式模块下。

模块树可能会让你想起你电脑上的文件系统的目录树;这是一个非常恰当的比较!就像文件系统的目录一样。就像文件系统中的目录,你用模块来组织你的代码。就像目录中的文件一样,我们需要一种方法来找到我们的模块。

3.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。

  • 相对路径从当前模块开始,使用 selfsuper或当前模块中的标识符。

绝对路径和相对路径后面都有一个或多个标识符,用双冒号(::)分开。

让我们回到清单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 hostingfn 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_restaurantfront_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 的模块开始的相对路径起作用。然后,因为 hostingadd_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 中使用 SoupSalad 的变体。除非它们的变体是公共的,否则枚举不是很有用;如果在每种情况下都要用 pub 来注释所有的枚举变体,那会很烦人,所以枚举变体的默认值是公共的。结构体通常在其字段不公开的情况下也很有用,所以结构体字段遵循默认为私有的一般规则,除非用pub来注释。

还有一种涉及 pub 的情况我们没有涉及,那就是我们最后一个模块系统特性:use 关键字。我们将首先介绍 use 本身,然后展示如何结合 pub 和 use。

3.3.2.5 - 用use关键字将路径纳入范围

用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_househosting。幸运的是,有一种方法可以简化这个过程。我们可以把一个路径带入一个作用域中,然后用 use 关键字来调用这个路径中的项目,就像它们是本地项目一样。

在清单 7-11 中,我们将 crate::front_of_house::hosting 模块带入 eat_at_restaurant 函数的作用域中,因此我们只需要指定 hosting::add_to_waitlist 来调用 eat_at_restaurantadd_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::hostinghosting 现在是该作用域中的一个有效名称,就像 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 关键字将一个名字带入作用域时,新作用域中的名字是私有的。为了使调用我们代码的代码能够引用该名称,就像它被定义在该代码的作用域中一样,我们可以结合 pubuse。这种技术被称为"再输出",因为我们把一个项目带入作用域,同时也使这个项目可以被其他人带入他们的作用域。

清单 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 作为一个依赖项,告诉 Cargocrates.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::iostd::io::Write 带入范围。

Glob 操作符

如果我们想把某个路径中定义的所有公共项目都带入作用域,我们可以在该路径后面指定*,即glob操作符。

use std::collections::*;

这个 use 语句将所有定义在 std::collection 中的公共项目带入当前的作用域。在使用 glob 操作符的时候要小心! glob 会使你更难分辨哪些名字在作用域中,以及你程序中使用的名字是在哪里定义的。

在测试时,glob 操作符经常被用来将所有被测试的东西带入测试模块;我们将在第11章的 “如何编写测试” 一节中讨论这个问题。glob 操作符有时也作为 prelude 模式的一部分使用:关于这种模式的更多信息,请参见标准库文档。

3.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.rsfront_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.3 - 对Rust的模块系统的清晰解释

对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

这些是我们应该能够消费我们的模块的不同方式:

rust-module-system-1

这3个例子应该足以解释Rust的模块系统如何工作。

例子1

让我们从第一个例子开始–在 main.rs 中导入 config.rs

// main.rs
fn main() {
  println!("main");
}
// config.rs
fn print_config() {
  println!("config");
}

每个人都会犯的第一个错误是,仅仅因为我们有 config.rshealth_route.rs 等文件,我们就认为这些文件是模块,我们可以从其他文件导入它们。

下面是我们看到的(文件系统树)和编译器看到的(模块树)。

rust-module-system-2

令人惊讶的是,编译器只看到 crate 模块,也就是我们的 main.rs 文件。这是因为我们需要在Rust中明确建立模块树–文件系统树和模块树之间没有隐式映射。

我们需要在Rust中显式建立模块树,没有隐式映射到文件系统中。

要将一个文件添加到模块树中,我们需要使用 mod 关键字将该文件声明为一个子模块。接下来让人困惑的是,你会认为我们在同一个文件中声明一个文件为模块。但我们需要在不同的文件中声明!因为我们只有 main.rs 。由于我们在模块树中只有 main.rs,让我们把 config.rs 声明为 main.rs 中的一个子模块。

mod 关键字声明一个子模块

mod关键字的语法是这样的:

mod my_module;

这里,编译器会在同一目录下寻找 my_module.rsmy_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.rsconfig.rs 在同一个目录下,让我们把 config 模块声明如下。

// main.rs
mod config;

fn main() {
  config::print_config();
  println!("main");
}
// config.rs
fn print_config() {
  println!("config");
}

我们使用 :: 语法访问 print_config 函数。

下面是模块树的样子:

rust-module-system-3

我们已经成功声明了 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.rsmy_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");
}

下面是模块树的样子:

rust-module-system-4

我们现在可以调用一个文件夹内的文件中定义的函数。

例子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");
}

下面是模块树的样子。

rust-module-system-5

等等,我们实际上还没有从 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_routecrate::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关键字用于缩短模块的路径

  • 我们不需要明确声明第三方模块

3.4 - Rust Lints

Rust Lints

3.4.1 - Rust Lints概述

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,所以它不是一个错误,但它可能是一个错误,所以你得到一个警告。

3.4.2 - Lints等级

Rustc的Lints等级

英文原文地址: https://doc.rust-lang.org/rustc/lints/levels.html

rustc 中,Lints分为四个级别

  1. 允许/allow
  2. 警告/warn
  3. 拒绝/deny
  4. 禁止/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() {}
  | ^^^^^^^^^^^^

所有四种等级,warnallowdeny,和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 以便在编译依赖项时通过,这样,即使它们有任何警告,也不会污染生成的输出。

3.4.3 - Lints分组

Rustc的Lints分组

备注:翻译自英文原文: https://doc.rust-lang.org/rustc/lints/groups.html

rustc 具有“Lint Group”的概念,您可以通过一个名称切换多个警告。

例如,nonstandard-style lint 一次性设置 non-camel-case-typesnon-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。这将为您已安装的特定编译器提供确切值。

3.4.4 - 默认为Allow的Lints

Rustc 默认为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
  |     ^^^^^^^^^^^^^^^^
  |

3.4.5 - 默认为Warn的Lints

Rustc 默认为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案件。

unstable-name-collision

此 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检测到ifmatchwhilereturn的括号; 这个括号是不需要的。触发此 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`
  |

3.4.6 - 默认为Deny的Lints

Rustc 默认为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”,并将其移动到其自己的目录(如果合适的话)来修复警告。

missing-fragment-specifier

当一个未使用的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的特定情况;

safe-extern-statics

在旧版本的 Rust 中,允许extern static以安全代码访问,会存在安全问题。这个 lint 现在抓住并否认这种代码。

unknown-crate-types

此 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>
   

3.5 - Rustup

Rust安装程序

3.5.1 - Rustup概述

Rust安装程序

介绍

rustup 是系统编程语言Rust的安装程序。

rustup 从官方发布渠道安装Rust编程语言,使你能够在稳定版、测试版和nightly编译器之间轻松切换并保持更新。它使交叉编译变得更加简单,为普通平台的标准库建立二进制。而且它可以在Rust支持的所有平台上运行。

相关信息

常用命令

安装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

镜像安装

参考: https://mirrors.tuna.tsinghua.edu.cn/help/rustup/

3.5.2 - Rustup Book学习笔记

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, defaultcomplete:

  • 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)时,会自动决定使用哪个工具链。有几种方法可以控制和覆盖哪个工具链被使用:

  1. 在命令行中使用的工具链覆盖简写,如 cargo +beta
  2. RUSTUP_TOOLCHAIN 环境变量。
  3. 目录覆盖,用 rustup override 命令设置。
  4. rust-toolchain.toml 文件。
  5. 默认的工具链。

工具链的选择是按照上面列出的顺序,使用第一个被指定的工具链。不过有一个例外:目录覆盖和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)

4 - Rust语法

Rust语法

4.1 - Rust语法速查表

Rust语法速查表

语法汇总和速查表。

基本语法

类型

let a = 1;  							// 定义变量,默认不可变
let b: bool = true;				// 明确指定变量类型
let mut x = 5;						// 定义可变变量
const MAX_POINTS: u32 = 100_000;   // 定义常量
let i:i32 = _f as i32;		// 转数据类型
type Int = i32;  					// 用关键字 type 为i32类型创建别名Int

// Rust 的 never 类型( ! )用于表示永远不可能有返回值的计算类型。
#![feature(never_type)] 	
let x:! = {
    return 123
};

// 对整行进行注释
/* ..  对区块注释  */
/// 生成库文档,一般用于函数或者结构体的说明,置于说明对象的上方
//! 也生成库文档,一般用于说明整个模块的功能,置于模块文件的头部

let tup: (i32, f64, u8) = (500, 6.4, 1);			// 元组(tuple)
let (x, y, z) = tup;													// 模式匹配(pattern matching)来解构元组值
(1,)   // 当元组中只有一个元素时,需要加逗号,即 
()  // 空元组,
let arr: [i32; 3] = [1, 2, 3];  // 数组
assert_eq!((1..5), Range{ start: 1, end: 5 }); // 范围类型,左闭右开
assert_eq!((1..=5), RangeInclusive::new(1, 5)); // 范围类型,全闭
let arr: [i32; 5] = [1, 2, 3, 4, 5];						// 固定大小数组的切片
let arr = &mut [1, 2, 3];												// 可变数组的切片
let vec = vec![1, 2, 3];												// 使用 vec! 宏定义的动态数组的切片
let str_slice: &[&str] = &["one", "two", "three"];		// 字符串数组的切片
pub struct People {															// Named-Field Struct
    name: &'static str,
    gender: u32,
} // 注意这里没有分号
let alex = People::new("Alex", 1); // 用 :: 来调用new方法,默认不可变
struct Color(i32, i32, i32); // 注意这里要有分号! Tuple-Like Struct,字段没有名字,只有类型
let color = Color(0, 1, 2);		// 直接构造,不用new方法
struct Integer(u32); // 当元组结构体只有一个字段的时候,称为 New Type 模式
struct Empty;					// 等价于  struct Empty {},单元结构体是没有任何字段的结构体。

enum Number {		// 无参数枚举
    Zero,
    One,
    Two,
}
enum Color {		// 类C枚举
    Red = 0xff0000,
    Green = 0x00ff00,
    Blue = 0x0000ff,
}
enum IpAddr {		// 带参数枚举
    V4(u8, u8, u8, u8),
    V6(String),
}
let mut v1 = vec![]; 					// 用宏创建可变向量
let v2 = vec![0; 10];					// 用宏创建不可变向量
let mut v3 = Vec::new();			// 用 new 方法创建向量

let a = [1,2,3]; 
let b = &a;										// 引用操作符 &,不可变,本质上是一种非空指针
let mut c = vec![1,2,3];			// 要获取可变引用,必须先声明可变绑定
let d = &mut c;								// 通过 &mut 得到可变引用

let mut x = 10;
let ptr_x = &mut x as *mut i32;			// 可变原始指针 *&mut T
let y = Box::new(20);
let ptr_y = &*y as *const i32;			// 不可变原始指针 *const T


pub fn math(op: fn(i32, i32) -> i32, a: i32, b: i32) -> i32{			/// 将函数作为参数传递
    op(a, b)				/// 通过函数指针调用函数
}
fn true_maker() -> fn() -> bool { is_true }				/// 函数的返回值是另外一个函数

let box_point = Box::new(Point { x: 0.0, y: 0.0 });		// 智能指针


流程处理

let big_n = if n < 10 && n > -10 { // if 不带括号,真不适应
    10 * n
} else {
    n / 2
};

for n in 1..101 {} 			// for … in 循环
while n < 101 {}				// while 循环
loop { }								// loop 循环,相当于一个 while true,需要程序自己 break

4.2 - Rust基本语法

Rust的基本语法

4.2.1 - Rust的常量

Rust的常量

常量是绑定到一个名称的不允许改变的值。

常量与不可变变量的区别:

  • 不允许对常量使用 mut
  • 声明常量使用 const 关键字而不是 let,并且 必须 注明值的类型。
  • 常量只能被设置为常量表达式,而不能是函数调用的结果,或任何其他只能在运行时计算出的值

备注:rust的常量是必须在编译时明确赋值的。

声明常量的例子:

const MAX_POINTS: u32 = 100_000;

4.2.2 - Rust的变量

Rust的变量

不可变变量

变量默认是不可改变的(immutable),这是Rust 提供的安全性和简单并发性来编写代码的众多方式之一。

let 关键字用于定义变量,默认定义的是不可变变量:

fn main() {
    // 可以通过类型推导得到变量类型,因此可以不制定变量类型
    let a = 1;
    // 也可以明确指定变量类型
    let b: bool = true;
}

Rust 编译器保证,如果声明一个值不会变,它就真的不会变。这意味着当阅读和编写代码时,不需要追踪一个值如何和在哪可能会被改变,从而使得代码易于推导。

可变变量

变量只是默认不可变;正如在第二章所做的那样,你可以在变量名之前加 mut 来使其可变。除了允许改变值之外,mut 向读者表明了其他代码将会改变这个变量值的意图。

fn main() {
    let mut x = 5;
    println!("The value of x is: {}", x);
    x = 6;
    println!("The value of x is: {}", x);
}

权衡使用

  • 使用大型数据结构时,适当地使用可变变量,可能比复制和返回新分配的实例更快
  • 对于较小的数据结构,总是创建新实例,采用更偏向函数式的编程风格,可能会使代码更易理解,为可读性而牺牲性能或许是值得的

当多种类型均有可能时,必须增加类型注解:

let guess: u32 = "42".parse().expect("Not a number!");

变量隐藏

可以定义一个与之前变量同名的新变量,而新变量会 隐藏(Shadowing) 之前的变量。

Rustacean 们称之为第一个变量被第二个 隐藏 了,这意味着使用这个变量时会看到第二个值。可以用相同变量名称来隐藏一个变量,以及重复使用 let 关键字来多次隐藏,如下所示:

fn main() {
    let x = 5;

    let x = x + 1;

    let x = x * 2;

    println!("The value of x is: {}", x);
}

当再次使用 let 时,实际上创建了一个新变量,我们可以改变值的类型,但复用这个名字:

let spaces = "   ";
let spaces = spaces.len();

4.2.3 - Rust的语句

Rust的语句

语句是指要执行的一些操作和产生副作用的表达式。

语句分为两种:

声明语句

用于声明各种语言项,包括声明变量,静态变量,常量,结构体,函数等,以及通过 extern 和 use 关键字引入包和模块等。

表达式语句

特指以分号结束的表达式。此类表达式求值结果将会被舍弃,并总是返回单元类型()

4.2.4 - Rust的表达式

Rust的表达式

表达式主要用于计算求值。

Rust 编译器在解析代码时:

  • 如果遇到分号,就会继续往后面执行
  • 如果遇到语句,就会执行语句
  • 如果遇到表达式,就会对表达式求值
  • 如果分号后面什么都没有,就会补上单元值()
  • 当遇到函数时,就会将函数体的花括号识别为块表达式。

块表达式

块表达式是由一对花括号和一系列表达式组成的,它总是返回块中最后一个表达式的值。

位置表达式

位置表达式(Place Expression)一般叫做左值,是表示内存位置的表达式,有以下几类:

  • 本地变量
  • 静态变量
  • 解引用 (* express)
  • 数组索引 (expr[expr])
  • 字段引用 (expr.field)
  • 位置表达式组合

通过位置表达式可以对某个数据单元的内存进行读写。位置表达式可以用于赋值。

值表达式

值表达式(Value Expression)一般叫做右值,值表达式引用了某个存储单元地址中的数据。它相当于数据,只能进行读操作。

从语义角度来说,位置表达式代表了持久性数据,值表达式代表了临时数据。位置表达式一般有持久的状态,值表达式要不是字面量,要不就是表达式求值过程中创建的临时值。

4.2.5 - Rust的注释

Rust的注释

Rust的注释有两种:

  1. 普通注释
    • // 对整行进行注释
    • /* .. */ 对区块注释
  2. 文档注释
    • /// 生成库文档,一般用于函数或者结构体的说明,置于说明对象的上方
    • //! 也生成库文档,一般用于说明整个模块的功能,置于模块文件的头部

示例:

/// # 文档注释: Sum函数
/// 该函数为求和函数
/// # usage:
///    assert_eq!(3, sum(1, 2));
fn sum(a: i32, b: i32) -> i32 {
    a + b
}
pub fn annotation() {
    // 这是单行注释的示例
    /*
         * 这是区块注释, 被包含的区域都会被注释
         * 你可以把/* 区块 */ 置于代码中的任何位置
    */
    /*
        注意上面区块注释中的*符号,纯粹是一种注释风格,
        实际并不需要
        */
    let x = 5 + /* 90 + */ 5;
    println!("Is `x` 10 or 100? x = {}", x);
    println!("2 + 3 = {}", sum(2, 3));
}

文档注释

文档注释支持 markdown !!

还支持对文档中的示例代码进行测试,可以用 rustdoc 工具生成 HTML 文档

4.2.6 - Rust的打印

Rust的打印

打印操作由std::fmt里面所定义的一系列宏来处理,包括:

  • format!:将格式化文本输出到 字符串(String)
  • print!:与 format!类似,但将文本输出到控制台
  • println!: 与 print!类似,但输出结果追加一个换行符

详细的使用说明见rust官方文档:

https://doc.rust-lang.org/1.0.0/std/fmt/index.html

4.2.7 - Rust的返回值

Rust的返回值

为什么使用返回值而不是异常

Why Rust uses Return Values for errors instead of Exceptions

我经常问自己,为什么Rust中的错误处理使用返回值而不是异常,在此进行解释,我引用在这里以备将来。

有些人需要在不允许使用 Exception 的地方使用Rust(因为展开表和清理代码太大)。这些人实际上包括所有浏览器供应商和游戏开发人员。此外, Exception 具有讨厌的代码生成权衡。您要么将它们设为零成本(如C ++,Obj-C和Swift编译器通常所做的那样),在这种情况下,在运行时抛出异常的代价非常高,或者使它们成为非零成本(如Java HotSpot和Go 6g/8g),在这种情况下,即使没有引发异常,您也会为每个 try 块(在Go中为defer)牺牲性能。对于使用RAII的语言,每个带有析构函数的堆栈对象都形成一个隐式try块,因此在实践中这是不切实际的。

零成本 Exception 的性能开销不是理论问题。我记得关于使用GCJ(使用零成本 Exception)进行编译时,Eclipse 需要花费30秒来启动的故事,因为它在启动时会引发数千个 Exception。

当您同时考虑错误和成功路径时,相对于 Exception,C处理错误的方法具有出色的性能和代码大小,这就是为什么系统代码非常喜欢它的原因。然而,它的人机工程学和安全性很差,Rust用Result来解决。Rust的方法形成了一种混合体,旨在实现C错误处理的性能,同时消除其陷阱。

4.3 - Rust的数据类型

Rust的数据类型

Rust 是 静态类型statically typed)语言,在编译时必须知道所有变量的类型。在 Rust 中,每一个值有明确的 数据类型data type),以便明确数据处理方式。

Rust 有两类数据类型子集:标量(scalar)和复合(compound)

标量类型

标量scalar)类型代表一个单独的值。

Rust 有四种基本的标量类型:

  1. 整型(integers)
  2. 浮点型(floating-point numbers)
  3. 布尔类型(Booleans)
  4. 字符(characters)

复合类型

复合类型Compound types)可以将多个值组合成一个类型。

Rust 有两个原生的复合类型:

  1. 元组(tuple)
  2. 数组(array)

4.3.1 - Rust的整型类型

Rust的整型类型

Rust 中的整型

Rust 内建的整数类型。在有符号列和无符号列中的每一个变体(例如,i16)都可以用来声明整数值的类型。

长度 有符号 无符号
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
arch isize usize

注意:isizeusize 类型依赖运行程序的计算机架构:64 位架构上它们是 64 位的, 32 位架构上它们是 32 位的。

整型字面值

可以使用表格中的任何一种形式编写数字字面值。注意除 byte 以外的所有数字字面值允许使用类型后缀,例如 57u8,同时也允许使用 _ 做为分隔符以方便读数,例如1_000

数字字面值 例子 说明
Decimal 98_222
Hex 0xff 16进制
Octal 0o77 8进制
Binary 0b1111_0000 2进制
Byte (u8 only) b'A' 字节字面量

那么该使用哪种类型的数字呢?如果拿不定主意,Rust 的默认类型通常就很好,数字类型默认是 i32:它通常是最快的,甚至在 64 位系统上也是。isizeusize 主要作为某些集合的索引。

整型溢出

“整型溢出”(“integer overflow” )在 Rust 中有一些有趣的规则:

  • 当在 debug 模式编译时,Rust 检查这类问题并使程序 panic

  • 在 release 构建中,Rust 不检测溢出,相反会进行一种被称为 “two’s complement wrapping” 的操作。

    简而言之,256 变成 0257 变成 1,依此类推。

    依赖溢出被认为是一种错误,即便可能出现这种行为。如果你确实需要这种行为,标准库中有一个类型显式提供此功能,Wrapping

4.3.2 - Rust的浮点型

Rust的浮点类型

Rust 有两个原生的 浮点数 f32f64,分别占 32 位和 64 位。

默认类型是 f64,因为在现代 CPU 中,它与 f32 速度几乎一样,不过精度更高。

let num = 3.1415926f64;
assert_eq!(-3.14, -3.14f64);
assert_eq!(2., 2.0f64);
assert_eq!(2e4, 20000f64);

特殊值:

use std::f32::{INFINITY, NEG_INFINITY, NAN, MIN, MAX};
println!("{:?}", INFINITY);
println!("{:?}", NEG_INFINITY);
println!("{:?}", NAN);
println!("{:?}", MIN);
println!("{:?}", MAX);

打印结果为:

inf
-inf
NaN
-340282350000000000000000000000000000000.0
340282350000000000000000000000000000000.0

4.3.3 - Rust的布尔型

Rust的布尔类型

Rust 内置布尔类型

Rust 中的布尔类型使用 bool 表示,可以通过as操作将bool转为数字0和1,但是不支持从数字转为bool:

fn main() {
    let _t = true;

    // 显式指定类型注解
    let _f: bool = false;

    // 用 as 转成 int
    let i:i32 = _f as i32;

    print!("{}", i);
}

使用布尔值的主要场景是条件表达式,例如 if 表达式。

标准库

https://doc.rust-lang.org/std/primitive.bool.html

bool代表一个值,它只能是true或false。如果你把bool 转为整数,那么true将是1,false将是0。

bool实现了各种 trait ,如BitAnd、BitOr、Not等,这些特征允许我们使用&、|和 ! 来执行布尔运算。

assert! 是测试中的一个重要的宏,用于检查一个表达式是否返回真值。

let bool_val = true & false | false;
assert!(!bool_val);

4.3.4 - Rust的字符型

Rust的字符类型

使用单引号来定义字符类型。

Rust 的 char 类型代表了一个 Unicode 标量值(Unicode Scalar Value),每个字符占4个字节。

fn main() {
    let x = 'r';
    let x = 'Ú';
    // 支持转义
    println!("{}", '\'');
    println!("{}", '\\');
    println!("{}", '\n');
    println!("{}", '\r');
    println!("{}", '\t');
    // 用 ASCII 码表示字符
    assert_eq!('\x2A', '*');
    assert_eq!('\x25', '%');
    // 用 unicode 表示字符
    assert_eq!('\u{CA0}', 'ಠ');
    assert_eq!('\u{151}', 'ő');
    // 可以使用 as 操作符将字符转为数字类型
    assert_eq!('%' as i8, 37);
    assert_eq!('ಠ' as i8, -96); //该字符值的高位会被截断,最终得到-96
}

4.3.5 - Rust的never类型

Rust的never类型

Rust 的 never 类型( ! )用于表示永远不可能有返回值的计算类型。

Rust 是一个类型安全的语言,所以需要将没有返回值的情况(如线程退出)纳入类型管理。

#![feature(never_type)]
let x:! = {
    return 123
};

报错:

error[E0554]: #![feature] may not be used on the stable release channel
 --> src/main.rs:1:1
  |
1 | #![feature(never_type)]
  | ^^^^^^^^^^^^^^^^^^^^^^^

never 是试验特性,需要使用 nightly 版本。

4.3.6 - Rust的元组(Tuple)类型

Rust的元组(Tuple)类型

元组(tuple)是一种异构有限序列:

  • 异构 指元组内的元素可以是不同的类型
  • 有限 是指元组有固定长度

创建元组

使用包含在圆括号中的逗号分隔的值列表来创建一个元组。元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的:

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);
}

tup 变量绑定到整个元组上,因为元组是一个单独的复合元素。

元组取值

为了从元组中获取单个值,可以使用模式匹配(pattern matching)来解构元组值:

fn main() {
    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;

    println!("The value of y is: {}", y);
}

除了使用模式匹配解构外,也可以使用点号(.)后跟值的索引来直接访问它们:

fn main() {
    let x: (i32, f64, u8) = (500, 6.4, 1);

    let five_hundred = x.0;

    let six_point_four = x.1;

    let one = x.2;
}

跟大多数编程语言一样,元组的第一个索引值是 0。

特殊元组

当元组中只有一个元素时,需要加逗号,即 (1,)

空元组,`()`

4.3.7 - Rust的数组(Array)类型

Rust的数组(Array)类型

数组(array)与元组不同,数组中的每个元素的类型必须相同。数组的特点是:

  • 数组大小固定
  • 元素均为相同类型
  • 默认不可变

可以通过 let mut 关键字定义可变绑定的 mut_arr,但是也只能通过下标修改数组元素的值。

数组的类型签名为 [T; N]:

  • T 是泛型标记,代表数组中元素的具体类型
  • N 是数组长度,是一个 编译时常量,必须在编译时确认值,而且不可改变。

Rust 中数组的定义和使用方式:

// 声明数组,默认不可变
let arr: [i32; 3] = [1, 2, 3];
// 声明可变数组
let mut mut_arr = [1, 2, 3];
assert_eq!(1, mut_arr[0]);
// 通过下标修改可变数组元素的值
mut_arr[0] = 3;
assert_eq!(3, mut_arr[0]);
// 创建初始值为0大小为10的数组
let init_arr = [0; 10];
assert_eq!(0, init_arr[5]);
assert_eq!(10, init_arr.len());
// 下标越界
// error: index out of bounds: the len is 3 but the index is 5
println!("{:?}", arr[5]);

如果下标越界,rust会以 panic 的方式报错。

数组内存分配

数组是在栈(stack)而不是在堆(heap)上为数据分配内存空间。

对于原始固定长度数组,只有实现了 Copy trait 的类型才能作为其元素,也就是说,只有可以在栈上存放的元素才可以存放在该类型的数组中。

未来,rust将支持VLA(variable-length array) 数组,即可变长度数组。

4.3.8 - Rust的范围(Range)类型

Rust的范围(Range)类型

Rust 内置的范围类型,包括 左闭右开 和 全币 两种区间,分别是 std::ops::Range 和 std::ops::RangeInclusive 的实例:

// (1..5)是结构体std::ops::Range的一个实例
use std::ops::{Range, RangeInclusive};
assert_eq!((1..5), Range{ start: 1, end: 5 });
// (1..=5)是结构体std::ops::RangeInclusive的一个实例
assert_eq!((1..=5), RangeInclusive::new(1, 5));
// 自带的 sum 方法用于求和
assert_eq!(3+4+5, (3..6).sum());
assert_eq!(3+4+5+6, (3..=6).sum());
(3..6)

// 每个范围都是一个迭代器,可用for 循环打印范围内的元素
for i in (1..5) {
    println!("{}", i);
}
for i in (1..=5) {
    println!("{}", i);
}

4.3.9 - Rust的切片(Slice)类型

Rust的切片(Slice)类型

Slice 切片是对一个数组(包括固定大小数组和动态数组)的引用片段,可以安全访问数组的一部分,而不需要拷贝。

在底层,切片表示为一个指向数组起始位置的指针和数组长度。

// 固定大小数组的切片
let arr: [i32; 5] = [1, 2, 3, 4, 5];
assert_eq!(&arr, &[1,2,3,4,5]);
assert_eq!(&arr[1..], [2,3,4,5]);
assert_eq!((&arr).len(), 5);
assert_eq!((&arr).is_empty(), false);

// 可变数组的切片
let arr = &mut [1, 2, 3];
arr[1] = 7;
assert_eq!(arr, &[1, 7, 3]);

//使用 vec! 宏定义的动态数组的切片
let vec = vec![1, 2, 3];
assert_eq!(&vec[..], [1,2,3]);

// 字符串数组的切片
let str_slice: &[&str] = &["one", "two", "three"];
assert_eq!(str_slice, ["one", "two", "three"]);

4.3.10 - Rust的结构体(Struct)类型

Rust的结构体(Struct)类型

Rust 提供三种结构体:

  • Named-Field Struct
  • Tuple-Like Struct
  • Unit-Like Struct

Named-Field Struct

Named-Field 是最常见的。

#[derive(Debug, PartialEq)]
pub struct People {
    name: &'static str,
    gender: u32,
} // 注意这里没有分号

impl People {
    // new 方法的参数并没有 &self
    fn new(name: &'static str, gender: u32) -> Self {
        return People { name: name, gender: gender };
    }
    // 读方法,传递的是 &self 不可变引用
    fn name(&self) {
        println!("name: {:?}", self.name);
    }
    // 写方法,传递的是 &mut self 可变引用
    fn set_name(&mut self, name: &'static str) {
        self.name = name;
    }
    fn gender(&self) {
        let gender = if self.gender == 1 { "boy" } else { "girl" };
        println!("gender: {:?}", gender);
    }
}

fn main() {
    // 用 :: 来调用new方法,默认不可变
    let alex = People::new("Alex", 1);
    // 调用其他方法用 . 号,不用传递 &self
    // 为啥不直接把 &self 改成类型java的this语法呢?反正也不传递
    alex.name();
    alex.gender();
    // 也可以直接构建结构体,绕过new方法
    assert_eq!(alex, People { name: "Alex", gender: 1 });

    // 创建可变结构体
    let mut alice = People::new("Alice", 0);
    alice.name();
    alice.gender();
    assert_eq!(alice, People { name: "Alice", gender: 0 });
    // 就可以调用set方法了
    alice.set_name("Rose");
    alice.name();
    assert_eq!(alice, People { name: "Rose", gender: 0 });
}

结构体名字要用驼峰法。

Tuple-Like Struct

元组结构体像元组和结构体的混合体:字段没有名字,只有类型:

#[derive(Debug, PartialEq)]
struct Color(i32, i32, i32); // 注意这里要有分号!

fn main() {
    // 直接构造,不用new方法
    let color = Color(0, 1, 2);
    assert_eq!(color.0, 0);
    assert_eq!(color.1, 1);
    assert_eq!(color.2, 2);
}

使用 . 号按下标访问字段。

当元组结构体只有一个字段的时候,称为 New Type 模式:

#[derive(Debug, PartialEq)]
struct Integer(u32);

// 用关键字 type 为i32类型创建别名Int
type Int = i32;  

fn main() {
    let int = Integer(10);
    assert_eq!(int.0, 10);

    let int: Int = 10;
    assert_eq!(int, 10);
}

Unit-Like Struct

单元结构体是没有任何字段的结构体。

// 等价于  struct Empty {}
struct Empty;
let x = Empty;
println!("{:p}", &x);
let y = x;
println!("{:p}", &y as *const _);
let z = Empty;
println!("{:p}", &z as *const _);

// struct RangeFull;  // 标准库源码中RangeFull就是一个单元结构体
assert_eq!((..), std::ops::RangeFull); //  RangeFull就是(..),表示全范围

单元结构体和 new type 模式类似,也相当于定义了一个新的类型。

单元结构体一般用于特定场景,标准库源码中RangeFull就是一个单元结构体。

参考资料

视频:

4.3.11 - Rust的枚举(Enum)类型

Rust的枚举(Enum)类型

枚举用 enum 关键字定义,有三种类型。

无参数枚举体

enum Number {
    Zero,
    One,
    Two,
}
let a = Number::One;
match a {
    Number::Zero => println!("0"),
    Number::One => println!("1"),
    Number::Two => println!("2"),
}

类C枚举

enum Color {
    Red = 0xff0000,
    Green = 0x00ff00,
    Blue = 0x0000ff,
}
println!("roses are #{:06x}", Color::Red as i32);
println!("violets are #{:06x}", Color::Blue as i32);

带参数枚举

enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}
let f: fn(u8, u8, u8, u8) -> IpAddr = IpAddr::V4;
let ff: fn(String) -> IpAddr = IpAddr::V6;
let home = IpAddr::V4(127, 0, 0, 1);

带参数枚举的值本质上属于函数指针类型:

  • fn(u8, u8, u8, u8) -> IpAddr
  • fn(String) -> IpAddr

参考资料

视频:

4.4 - Rust的字符串

Rust的字符串

出于内存安全的考虑,Rust将字符串分成两种类型:

  1. str字符串:固定长度,不可改变
  2. String字符串:可变长度

4.4.1 - Rust中的str字符串

Rust中的str字符串

Rust的原始字符串类型,也称为 字符串切片。

通常以不可变借用的形式存在,既 &str

str 由两部分组成:

  • 指向字符串序列的指针
  • 记录长度的值
use std::slice::from_raw_parts;
use std::str::from_utf8;

let truth: &'static str = "Rust是一门优雅的语言";
let ptr = truth.as_ptr();
let len = truth.len();
assert_eq!(28, len);

let s = unsafe {
    let slice = from_raw_parts(ptr, len);
    from_utf8(slice)
};
assert_eq!(s, Ok(truth));

Rust 中的字符串本质上是一段有效的 UTF-8 字符序列。

4.4.2 - Rust中的string字符串

Rust中的string字符串

字符串编码

// 将UTF-8序列转为字符串
let tao = std::str::from_utf8(&[0xE9u8, 0x81u8, 0x93u8]).unwrap();
assert_eq!("道", tao);

// 将16进制Unicode码位转为字符串
assert_eq!("道", String::from("\u{9053}")); 

let unicode_x = 0x9053;
let utf_x_hex = 0xe98193;
let utf_x_bin  = 0b111010011000000110010011;
println!("unicode_x: {:b}", unicode_x);
println!("utf_x_hex: {:b}", utf_x_hex);
println!("utf_x_bin: 0x{:x}", utf_x_bin);

4.5 - Rust的集合

Rust的集合

4.5.1 - Rust集合中的向量(Vec)

Rust集合中的向量(Vec)

向量(Vec) 也是一种数组,差别在于可动态增长。

// 用宏创建可变向量
let mut v1 = vec![];
v1.push(1);
v1.push(2);
v1.push(3);
assert_eq!(v1, [1,2,3]);
assert_eq!(v1[1], 2);

// 用宏创建不可变向量
let v2 = vec![0; 10];

// 用 new 方法创建
let mut v3 = Vec::new();
v3.push(4);
v3.push(5);
v3.push(6);

4.5.2 - Rust集合中的双端队列(VecDeque)

Rust集合中的双端队列(VecDeque)

Rust 中的 VecDeque 是基于可增长的 RingBuffer 算法实现的双端队列。

use std::collections::VecDeque;
let mut buf = VecDeque::new();

buf.push_front(1);
buf.push_front(2);
assert_eq!(buf.get(0), Some(&2));
assert_eq!(buf.get(1), Some(&1));

buf.push_back(3);
buf.push_back(4);
buf.push_back(5);

assert_eq!(buf.get(2), Some(&3));
assert_eq!(buf.get(3), Some(&4));
assert_eq!(buf.get(4), Some(&5));

4.5.3 - Rust集合中的链表(LinkedList)

Rust集合中的链表(LinkedList)

Rust 提供的链表是双向链表。

最好是使用 Vec 或者 VecDeque 类型,比链表更快。

use std::collections::LinkedList;
let mut list1 = LinkedList::new();

list1.push_back('a');

let mut list2 = LinkedList::new();
list2.push_back('b');
list2.push_back('c');

list1.append(&mut list2);
println!("{:?}", list1); // ['a', 'b', 'c']
println!("{:?}", list2); // []

list1.pop_front();
println!("{:?}", list1); // ['b', 'c']

list1.push_front('e');
println!("{:?}", list1); // ['e', 'b', 'c']

list2.push_front('f');
println!("{:?}", list2); // ['f']

4.5.4 - Rust集合中的映射(Map)

Rust集合中的映射(Map)

Rust 提供了两个 Key-Value 哈希映射表:

  • HashMap<K, V>:无序
  • BTreeMap<K, V>:有序

要求:Key 必须是可哈希的类型,Value 必须满足是在编译期已知大小的类型。

use std::collections::BTreeMap;
use std::collections::HashMap;
let mut hmap = HashMap::new();
let mut bmap = BTreeMap::new();
hmap.insert(3, "c");
hmap.insert(1, "a");
hmap.insert(2, "b");
hmap.insert(5, "e");
hmap.insert(4, "d");
bmap.insert(3, "c");
bmap.insert(2, "b");
bmap.insert(1, "a");
bmap.insert(5, "e");
bmap.insert(4, "d");
// 输出结果为:{1: "a", 2: "b", 3: "c", 5: "e", 4: "d"},但key的顺序是随机的,因为HashMap是无序的
println!("{:?}", hmap);
// 输出结果永远都是 {1: "a", 2: "b", 3: "c", 4: "d", 5: "e"},因为BTreeMap是有序的
println!("{:?}", bmap);

4.5.5 - Rust集合中的集合(Set)

Rust集合中的集合(Set)

Rust 提供了两个哈希集合:

  • HashSet<K>:无序,等同于 HashMap<K, ()>,值为空元组的特定类型
  • BTreeSet<K>:有序,等同于 BTreeMap<K, ()>,值为空元组的特定类型

特性如下:

  • 集合中的元素是唯一的
  • 集合中的元素是可哈希的类型
use std::collections::HashSet;
use std::collections::BTreeSet;
let mut hbooks = HashSet::new();
let mut bbooks = BTreeSet::new();
// 插入数据
hbooks.insert(2);
hbooks.insert(1);
hbooks.insert(2);
// 判断元素是否存在,contains方法和HashMap中的一样
if !hbooks.contains(&1) {
}
println!("{:?}", hbooks);
bbooks.insert(1);

bbooks.insert(2);
bbooks.insert(3);
println!("{:?}", bbooks); // 输出固定为 {1, 2, 3} ,因为是有序

4.5.6 - Rust集合中的优先队列(BinaryHeap)

Rust集合中的优先队列(BinaryHeap)

Rust 提供的优先队列是基于二叉最大堆(Binary Heap)实现的。

use std::collections::BinaryHeap;
let mut heap = BinaryHeap::new();
assert_eq!(heap.peek(), None);
heap.push(93);
heap.push(80);
heap.push(48);
heap.push(53);
heap.push(72);
heap.push(30);
heap.push(18);
heap.push(36);
heap.push(15);
heap.push(35);
heap.push(45);
assert_eq!(heap.peek(), Some(&93));
println!("{:?}", heap);  // [93, 80, 48, 53, 72, 30, 18, 36, 15, 35, 45]

4.6 - Rust的指针

Rust的指针

4.6.1 - Rust的指针概述

Rust的指针概述

Rust 中将可以表示内存地址的类型成为 指针

Rust提供了多种类型的指针:

  • 引用(Reference)
  • 原生指针(Raw Pointer)
  • 函数指针(fn Pointer)
  • 智能指针(Smart Pointer)

Rust 可以划分为 Safe Rust 和 Unsafe Rust 两部分。

Safe Rust

引用主要应用于 Safe Rust。

在Safe Rust 中,编译器会对引用进行借用检查,以保证内存安全和类型安全。

Unsafe Rust

原生引用主要用于 Unsafe Rust。

原生引用不在 Safe Rust 的控制范围内,需要编程人员自己保证安全。

4.6.2 - Rust的引用(Reference)

Rust的引用(Reference)

Rust 提供引用操作符 &,可以直接获取表达式的存储单元地址,即内存地址。

引用本质上是一种非空指针。

let a = [1,2,3];
let b = &a;
println!("{:p}", b); // 0x7ffcbc067704

// 要获取可变引用,必须先声明可变绑定
let mut c = vec![1,2,3];
// 通过 &mut 得到可变引用
let d = &mut c;
d.push(4);
println!("{:?}", d); // [1, 2, 3, 4]

let e = &42;
assert_eq!(42, *e);

从语义上说,不管是 &a 还是 &mut c,都是对原有变量的所有权的借用,所以引用也被称为借用。

4.6.3 - Rust的原始引用(Raw Reference)

Rust的原始引用(Raw Reference)

Rust 支持两种原始引用:

  • 不可变原始指针 *const T
  • 可变原始指针 *&mut T

用 as 操作符可以将引用转为原始指针:

let mut x = 10;
let ptr_x = &mut x as *mut i32;
let y = Box::new(20);
let ptr_y = &*y as *const i32;

// 原生指针操作要放在unsafe中执行
unsafe {
    *ptr_x += *ptr_y;
}
assert_eq!(x, 30);

4.6.4 - Rust的函数指针(Fn Pointer)

Rust的函数指针(Fn Pointer)

函数在Rust中是一等公民,函数自身就可以作为函数的参数和返回值使用。

函数作为参数

​```
/// 将函数作为参数传递
pub fn math(op: fn(i32, i32) -> i32, a: i32, b: i32) -> i32{
    /// 通过函数指针调用函数
    op(a, b)
}
fn sum(a: i32, b: i32) -> i32 {
    a + b
}
fn product(a: i32, b: i32) -> i32 {
    a * b
}

let a = 2;
let b = 3;
assert_eq!(math(sum, a, b), 5);
assert_eq!(math(product, a, b), 6);
​```

函数作为返回值

fn is_true() -> bool { true }
/// 函数的返回值是另外一个函数
fn true_maker() -> fn() -> bool { is_true }
/// 通过函数指针调用函数
assert_eq!(true_maker()(), true);
​```

4.6.5 - Rust的智能指针(Smart Pointer)

Rust的智能指针(Smart Pointer)

智能指针 (Smart Pointer) 源自c++,Rust 引入后成为 Rust 语言中最重要的一种数据结构。

功能介绍

Rust 中的值默认被分配到 栈内存。可以通过 Box<T> 将值装箱(在堆内存中分配)。

  • Box<T> 是指向类型为T的堆内存分配值的智能指针。
  • Box<T> 超出作用域范围时,将调用其析构函数,销毁内部对象,并自动释放内存。
  • 可以通过解引用操作符来获取Box<T>中的T

Box<T> 的行为像引用,并可以自动释放内存,所以称为智能指针。

Box<T>类型

Rust 中提供了很多智能指针类型。

#[derive(Debug, PartialEq)]
struct Point {
    x: f64,
    y: f64,
}
let box_point = Box::new(Point { x: 0.0, y: 0.0 });
let unboxed_point: Point = *box_point;
assert_eq!(unboxed_point, Point { x: 0.0, y: 0.0 });

4.7 - Rust的流程控制

Rust的流程控制

Rust 中不叫流程控制语句,而是叫做流程控制表达式。

这是关键:流程表达式是可以作为右值赋值的

4.7.1 - Rust的if条件表达式

Rust的if条件表达式

表达式一定会有值,所以 if 条件表达式的分支必须返回同一个类型的值。

let n = 13;
// if 表达式可以用来赋值
let big_n = if n < 10 && n > -10 {
    // 分支必须返回同一个类型的值
    10 * n
} else {
    // 自动截取
    n / 2
};
assert_eq!(big_n, 6);

4.7.2 - Rust的for ... in循环表达式

Rust的for … in循环表达式

循环表达式

Rust 有三种循环表达式:while 、loop 和 for … in 表达式。

for … in 循环:

for n in 1..101 {
    if n % 15 == 0 {
        println!("fizzbuzz");
    } else if n % 3 == 0 {
        println!("fizz");
    } else if n % 5 == 0 {
        println!("buzz");
    } else {
        println!("{}", n);
    }
}

注意 for … in 后面是一个 Rang 类型,左闭右开,所以这个循环的最后一个n值是100。

4.7.3 - Rust的loop循环表达式

Rust的loop循环表达式

loop 循环,相当于一个 while true,需要程序自己 break:

let mut n = 1;
loop {
    if n > 101 { break; }
    if n % 15 == 0 {
        println!("fizzbuzz");
    } else if n % 3 == 0 {
        println!("fizz");
    } else if n % 5 == 0 {
        println!("buzz");
    } else {
        println!("{}", n);
    }
    n += 1;
}

强调:当使用无限循环时,务必使用 loop,避免使用 while true。

4.7.4 - Rust的while循环表达式

Rust的while循环表达式

while 循环,没啥特别:

let mut n = 1;
while n < 101 {
    if n % 15 == 0 {
        println!("fizzbuzz");
    } else if n % 3 == 0 {
        println!("fizz");
    } else if n % 5 == 0 {
        println!("buzz");
    } else {
        println!("{}", n);
    }
    n += 1;
}

4.7.5 - Rust的match表达式

Rust的match表达式

match用于匹配各种情况,类似其他语言的 switch 或 case。

在 Rust 语言中,match 分支使用 模式匹配 (pattern matching)技术,match分支:

  • 左边是模式:
    • 不同分支可以是不同的模式
    • 必须穷尽每一种可能,所以通常最后使用通配符 _
  • 右边是执行代码
    • 同样所有分支必须返回同一个值
let number = 42;
match number {
    // 模式为单个值
    0 => println!("Origin"),
    // 模式为Range
    1...3 => println!("All"),
    // 模式为 多个值
    | 5 | 7 | 13  => println!("Bad Luck"),
    // 绑定模式,将模式中的值绑定给一个变量,供右边执行代码使用
    n @ 42 => println!("Answer is {}", n),
    // _ 通配符处理剩余情况
    _ => println!("Common"),
}

match语句可以直接用来赋值,代码比较简练:

let boolean = true;
let binary = match boolean {
    false => 0,
    true => 1,
};

4.7.6 - Rust的 if let 表达式

Rust的 if let 表达式

if let 表达式用来在某些场合替代 match 表达式.

let boolean = true;
let mut binary = 0;
// if let 左侧为模式,右侧为匹配的值
if let true = boolean {
    binary = 1;
}
assert_eq!(binary, 1);

备注:这个例子看不出 if let 的价值所在。

4.7.7 - Rust的 while let 表达式

Rust的 while let 表达式

while let 可以简化代码,如这个loop:

let mut v = vec![1,2,3,4,5];
loop {
    match v.pop() {
        Some(x) => println!("{}", x),
        None => break,
    }
}

可以改写为:

let mut v = vec![1,2,3,4,5];
while let Some(x) = v.pop() {
    println!("{}", x);
}

5 - Rust类型系统

5.1 - Rust类型系统概述

Rust类型系统概述

5.2 - Rust类型通用概念

Rust类型通用概念

5.2.1 - [Rust编程之道笔记]通用概念

Rust编程之道一书 3.1 节通用概念

内容出处: Rust编程之道一书,第3章类型系统,3.1 通用概念

3.1 通用概念

所谓类型,其实就是对表示信息的值进行细粒度的区分。不同的类型占用的内存不同。与直接操作比特位相比,直接操作类型可以更安全、更有效的利用内存。

类型系统是编程语言的基础和核心。

**在类型系统中,一切皆类型。**基于类型定义的一系列组合、运算和转换,可以看做类型的行为。

3.1.1 类型系统的作用

  • 排查错误:在编译期或者运行期进行类型检查。静态语言能在编译期排查出错误。
  • 抽象:类型容许开发者在更高层面上进行思考。
  • 文档:明确的类型声明可以表明程序的行为
  • 优化效率:对于静态编译语言,在编译期可以通过类型检查来优化操作,节省运行时时间
  • 类型安全
    • 类型安全的语言可以便面类型间的无效计算
    • 类型安全的语言还可以保证内存安全:避免空指针、悬垂指针和缓存区溢出等导致的内存安全问题
    • 类型安全的语言可以避免语义上的逻辑错误

3.1.2 类型系统的分类

  • 静态类型:在编译期进行类型检查
  • 动态类型:在运行期进行类型检查

编程语言:

  • 强类型:不容许类型的自动隐式转换,在强制转换前不同类型无法进行计算
  • 弱类型:容许类型的自动隐式转换

3.1.3 类型系统与多态性

多态类型系统的定义:容许一段代码在不同的上下文中具有不同类型的类型系统。对于静态类型的语言而言,多态性的好处是可以在不影响类型丰富的前提下,为不同的类型编写通用的代码。

按照多态发生的时机来划分:

  • 静多态(Static Polymorphism) :发生在编译期,静多态牺牲灵活性获取性能
  • 动多态(Dynamic Polymorphism):发生在运行时,动多态牺牲性能获取灵活性。

动多态在运行时需要查表,占用较多空间,所以一般情况下都使用静多态。

Rust 语言同时支持静多态和动多态,其中静多态是一种零成本抽象。

现代编程语言包含三种多态形式:

  • 参数化多态(Parametric polymorphism)

    一般是静多态,实际就是指泛型。泛型使得语言极具表达力,同时也保证了静态类型安全。

  • Ad-hoc 多态(ad-hoc polymorphism)

    也叫特定多态。

    Ad-hoc 多态是指同一种行为定义,在不同的上下文中会有不同的行为实现。

    Rust 受 Haskell 启发,使用 trait 来支持 Ad-hoc 多态。

  • 子类型多态(subtype polymorphism)

    一般是动多态,常用于面向对象的语言中,如Java。子类型多态代表一种包含关系:父类型的值包含了子类型的值,所以子类型的值可以看成父类型的值。

    Rust 中没有类型Java的继承概念,因此不存在子类型多态。

总结:Rust 的类型系统目前只支持参数化多态和Ad-hoc多态,即泛型和trait。

5.3 - Rust类型系统概述

Rust类型系统概述

5.3.1 - Rust类型大小

Rust类型大小

5.3.1.1 - [Rust编程之道笔记]类型大小

Rust编程之道一书 3.2.1 节类型大小

内容出处: Rust编程之道一书,第3章类型系统,3.2.1 类型大小

3.2 类型大小

编程语言中不同的类型本质上是内存占用空间和编码方式的不同,Rust也不例外。Rust没有GC,内存首先由编译期来分配,rust代码首先由编译期来分配, Rust 代码被编译为 LLVM IR,其中携带了内存分配的信息。

因此,Rust编译器需要实现知道类型的大小,才能分配合理的内存

可确定大小类型和动态大小类型

Rust 中绝大部分类型都是在编译期内可确定大小的类型(称为Sized Type),也有少量的动态大小的类型(Dynamic Sized Type,简写为 DST)。

对于DST,Rust提供了引用类型,因为引用总会有固定且在编译期已知的大小。如字符串切片 &str 就是一种引用类型 ,由指针和长度信息组成。

零大小类型

除了可确定大小类型和 DST 类型,Rust 还支持零大小类型(Zero Sized Type, ZST),如单元类型和单元结构体。

  • 单元类型和单元结构体大小为零
  • 由单元类型组成的数组大小也为零

ZST类型的特点是:值就是本身,运行时并不占用内存空间。

底类型

底类型(Bottom type)是院子类型理论的术语,其实是 never 类型,它的特点是:

  • 没有值
  • 是其他任意类型的子类型

如果说 ZST 类型表示“空”的话,那么底类型就是表示“无”。

Rust 中的底类型用 ! 表示,也被称为 Bang Type。

Rust 中有很多中情况确实没有值,但是为了类型安全,必须归入类型系统进行统一处理,这些情况包括:

  • 发散函数(Diverging Function):指会导致线程崩溃的 panic! 或者用于退出函数的 std::process::exit
  • continuebreak 关键字: 只是表示流程的跳转,并不会返回什么
  • loop 循环:虽然可以返回某个值,但是也有需要无限循环的时候
  • 空枚举,比如 enum Void{}

5.3.2 - Rust类型推导

Rust类型推导

5.3.2.1 - [Rust编程之道笔记]类型推导

Rust编程之道一书 3.2.2 节类型推导

内容出处: Rust编程之道一书,第3章类型系统,3.2.2 类型推导

3.2.2 类型推导

Rust 只能在局部范围内进行类型推导。

Turbofish操作符

当 Rust 无法从上下文中自动推导出类型的时候,编译期会通过错误信息提示并要求添加类型标注。

标注类型的方式:

fn main() {
    let x = "1";
    //标注变量的类型
    let int_x : i32 = x.parse().unwrap();
    //通过 Turbofish操作符 标注
    assert_eq!(x.parse::<i32>().unwrap(), 1);
}

类型推导的不足

总结:rust的类型推导还不够强大, 因此,编码时推荐尽量显式声明类型,避免麻烦。

5.4 - Rust的范型

Rust的范型

5.4.1 - Rust的范型概述

Rust的范型概述

5.4.2 - [Rust编程之道笔记]泛型

Rust编程之道一书 3.3 节泛型

内容出处: Rust编程之道一书,第3.3节泛型

3.3 泛型

Rust的泛型是参数化多态。简单说就是把泛化的类型作为参数,单个类型可以抽象为一簇类型。

泛型的实现

泛型类型

类似 Box, Option, Result<T, E> 都是泛型类型。

泛型函数

fn foo<T>(x: T) -> T {
    return x;
}

泛型结构体

struct Point<T> {
    x: T,
    y: T,
}
impl<T> Point<T> {
    fn new(x:T, y:T) -> Self {
        Point{x:x, y:y}
    }
}

泛型枚举

TDB

泛型单态化

Rust的泛型是静多态,是编译期多态,在编译期会被单态化(Monomorphization)。

单态化意味着编译期要将一个泛型函数生成多个具体类型对应的函数。

泛型和单态化是 Rust 最重要的功能。

单态化静态分发的好处是性能好,没有运行时开销;缺点是容易造成编译后生成的二进制文件膨胀。

3.3.2 泛型返回值自动推导

编译期可以对泛型进行自动推导。

#[derive(Debug, PartialEq)]
struct Foo(i32);
#[derive(Debug, PartialEq)]
struct Bar(i32, i32);
trait Inst {
    fn new(i: i32) -> Self;
}
impl Inst for Foo {
    fn new(i: i32) -> Foo {
        Foo(i)
    }
}
impl Inst for Bar {
    fn new(i: i32) -> Bar {
        Bar(i, i + 10)
    }
}
// 泛型函数,返回值是泛型
fn foobar<T: Inst>(i: i32) -> T {
    T::new(i)
}
// 指定泛型类型为Foo,因此foobar实现中调用的是 Foo::new(i)
let f: Foo = foobar(10);
assert_eq!(f, Foo(10));
// 指定泛型类型为Bar,因此foobar实现中调用的是 Bar::new(i)
let b: Bar = foobar(20);
assert_eq!(b, Bar(20, 30));

5.5 - Rust的Trait

Rust的Trait

5.5.1 - Rust的Trait概述

Rust的Trait概述

5.5.2 - [Rust编程之道笔记]泛型

Rust编程之道一书 3.4 节深入trait

内容出处: Rust编程之道一书,第3.4节深入trait

3.4 深入trait

Trait 是 Rust 的灵魂:

  • Rust 中所有的抽象都是基于trait来实现
    • 接口抽象
    • OOP范式抽象
    • 函数式范式抽象
  • trait 也保证了这些抽象几乎都是在运行时零开销

Trait 是 Rust 对 Ad-hoc 多态的支持。

从语义上说,trait 是行为上对类型的约束,这种约束让 trait 有如下用法:

  • 接口抽象:接口是对类型行为的统一约束
  • 泛型约束:泛型的行为被trait限定在更有限的范围内
  • 抽象类型:在运行时作为一种间接的抽象类型去使用,动态分发给具体的类型
  • 标签trait:对类型的约束,可以直接作为一种“标签”使用

3.4.1 接口抽象

trait 最基础的用法就是进行接口抽象:

  • 接口中可以定义方法,并支持默认实现
  • 接口中不能实现另外一个接口,但是接口之间可以继承
  • 同一个接口可以同时被多个类型实现,但不能被同一个类型实现多次
  • 使用 impl 关键字为类型实现接口方法
  • 使用 trait 关键字定义接口

同一个trait,在不同上下文中实现的行为不同。为不同的类型实现trait,术语一种函数重载。

关联类型

rust中的很多操作符都是基于 trait 来实现的。(参见 core::ops

// Add<RHS = Self> 为类型参数RHS指定默认值为Self
// Self 是每个trait都带有的隐式类型参数,代表实现当前trait的具体类型
trait Add<RHS = Self> {
    // 用 type 关键字定义参数类型
    // 以这种方式定义的类型叫做关联类型
    type Output;
    // 返回类型可以用 Self::Output 来指定
    fn add(self, rhs: RHS) -> Self::Output;
}

// 标准库中u32类型的实现,类型参数默认是 Self
impl Add for $t {
    type Output = $t;
    fn add(self, other: $t) -> $t {
        self + other
    }
}

// 标准库中 String 类型的实现,类型参数通过 Add<&str> 显示指定为 &str
impl Add<&str> for String {
    // 实现时指定 Output 类型
    type Output = String;
    fn add(mut self, other: &str) -> String {
        self.push_str(other);
        self.push_str("!!!");
        self
    }
}

fn main() {
    let a = "hello";
    let b = " world";
    let c = a.to_string().add( b);
    println!("{:?}", c); // "hello world"
}

使用关联类型能够使得代码更加精简,也对方法的输入和输出进行了很好的隔离,增强代码的可读性。

在语义层面,使用关联类型增强了 trait 表示行为的语义,因为它表示和某个行为(trait)相关联的类型。在工程上,也体现了高内聚的特点。

trait一致性

孤儿规则(Orphan Rule):

如果要实现某个trait,那么该trait和要实现该trait的类型至少有一个要在当前crate中定义。

// 不能使用标准库中的Add,需要在当前crate中定义Add
trait Add<RHS=Self> {
    type Output;
    fn add(self, rhs: RHS) -> Self::Output;
}
impl Add<u64> for u32{
    type Output = u64;
    fn add(self, other: u64) -> Self::Output {
        (self as u64) + other
    }
}
let a = 1u32;
let b = 2u64;
// 要用add方法,而不是+操作符
assert_eq!(a.add(b), 3);

trait继承

Rust不支持对象继承,但是支持 trait 继承。

子trait可以继承父trait中定义或者实现的方法。

    trait Page{
        fn set_page(&self, p: i32){
            println!("Page Default: 1");
        }
    }
    trait PerPage{
        fn set_perpage(&self, num: i32){
            println!("Per Page Default: 10");
        }
    }
	// 冒号表示继承,加号表示继承有多个trait
    trait Paginate: Page + PerPage{
        fn set_skip_page(&self, num: i32){
            println!("Skip Page : {:?}", num);
        }
    }
	// 为泛型T实现Paginate
	// 而泛型T定义为实现了Page + PerPage
	// 好处就是可以自动实现各种类型的Impl,而不必如下面显式声明
    impl <T: Page + PerPage>Paginate for T{}

    struct MyPaginate{ page: i32 }
    impl Page for MyPaginate{}
    impl PerPage for MyPaginate{}
	// 可以手工实现对Paginate的impl
	// 也可以通过上面的泛型T自动实现
	//impl Paginate for MyPaginate{}
    let my_paginate = MyPaginate{page: 1};
    my_paginate.set_page(2);
    my_paginate.set_perpage(100);
    my_paginate.set_skip_page(12);

3.4.2 泛型约束

使用泛型编程时,很多情况下,并不是针对所有的类型。因此需要用trait作为泛型的约束。

trait限定

use std::ops::Add;
// 如果不加类型约束,只有一个泛型T,则不能保证所有类型都实现了+操作
//fn sum<T>(a: T, b: T) -> T {
// 因此需要为泛型增加约束,限制为实现了Add trait的类型
// 可以简写为 Add<Output=T>
//fn sum<T: Add<Output=T>>(a: T, b: T) -> T {
fn sum<T: Add<T, Output=T>>(a: T, b: T) -> T {
    a + b
}
assert_eq!(sum(1u32, 2u32), 3);
assert_eq!(sum(1u64, 2u64), 3);

Add<T, Output=T> 通过类型参数确定了关联类型Output也是T,因此可以省略类型参数T,简写为 Add<Output=T>

使用 trait 对泛型进行约束,叫做 trait 限定。

语法上也可以使用 where 关键字做约束:

fn sum<T>(a: T, b: T) -> T where T: Add<T, Output=T> {
    a + b
}

看上去更清晰一些。

理解trait限定

Rust 中的 trait 限定也是 structural typing 的一种实现,可以看做一种静态 Duck Typing。

类型可以看作是具有相同属性值的集合。trait 也是一种类型,是一种行为的集合。

trait Paginate: Page + Perpage

:代表"包含于",+代表交集。

3.4.3 抽象类型

Trait 可以用作抽象类型(abstract type)。

抽象类型无法直接实例化。对于抽象类型,编译器无法确定其确切的功能和内存大小。

目前Rust中有两种方法来处理抽象类型:trait object 和 impl trait

Trait对象

将拥有相同行为的类型抽象为一个类型,这就是 trait 对象。

备注:在这个使用方法上,trait 很类似 Java 中的 interface。

trait本身也是一种类型,但它的类型大小在编译期是无法确定的,所以trait对象必须使用指针。可以利用引用操作符 & 或者 Box<T> 来制造一个 trait 对象。

trait对象等价于如下所示的结构体:

pub struct TraitObject {
    pub data: *mut (),
    pub vtable: *mut (),
}

TraitObject 包含两个指针:

  • data指针:指向 trait 对象保存的类型数据T
  • vtable指针:指向包含为 T 实现的对象的 Vtable(虚表)

在运行时,当有 trait_object.method() 被调用时,Trait Object 会根据虚表指针从虚表中查出正确的指针,然后再进行动态调用。这也是将 trait 对象称为动态分发的原因。

对象安全

当 trait 对象在运行期进行动态分发时,必须确定大小。因此必须满足以下两条规则的 trait 才可以作为 trait 对象使用:

  • trait 的 Self 类型参数不能被限定为 Sized
  • trait 中所有的方法都必须是对象安全的

当不希望trait 作为 trait 对象时,可以使用 Self:Sized 进行限定。

而对象安全的方法必须满足以下三点:

  1. 方法受 Self:Sized 约束

  2. 方法签名同时满足以下三点

    • 必须不包含任何泛型参数。如果包含泛型,trait对象在虚表中查找方法时将不能确定该调用哪个方法

    • 第一个参数必须是 Self 类型或者可以解引用为 Self 的类型。也就是说,必须有接受者,如 self, &self, &mut selfself: Box<Self>

    • Self 不能出现在除第一个参数之外的地方,包括返回值中。

      总结:没有额外 Self 类型参数的非泛型成员方法

  3. trait中不能包含关联常量(Associated Constant)

备注:书上写的很晦涩,看不太懂,稍后找点其他文章看看。

Impl Trait

在 Rust 2018 版本中,引入了可以静态分发的抽象类型 impl Trait。

如果说 trait object 是装箱抽象类型(boxed abstract type),那么 impl trait 就是拆箱抽象类型(unboxed abstract type)。“装箱” 代表把值托管到堆内存,而“拆箱”则是在栈内存中生成新的值。总之:装箱抽象类型代表动态分发,拆箱抽象类型代表静态分发。

目前 impl trait 只可以在输入的参数和返回值这两个位置使用。

TBD:确认一下最新edition 2021中是否有改变。

// 参数使用 impl Fly + Debug 抽象类型
fn fly_static(s: impl Fly + Debug) -> bool {
    f.fly()
}

// 返回值指定 impl Fly 抽象类型
fn can_fly(s: impl Fly + Debug) -> impl Fly {
    if s.fly() {
        println!("{:?} can fly", s);
    } else {
        println!("{:?} can;t fly", s);
    }
    s
}

注意: impl trait 只能用于为单个参数指定抽象类型,如果对多个参数使用 impl trait 语法,编译期会报错。

Dynamic trait

在 rust 2018 版本中,为了在语义上和 impl trait 语法相对应,专门为动态分发的 trait 对象增加了新的语法 dyn Trait,其中 dyn 是 dynamic 的缩写。

impl trait 代表静态分发, dyn trait 代表动态分发。

// 返回值指定 impl Fly 抽象类型
fn dyn_can_fly(s: impl Fly + Debug + 'static) -> Box<dyn Fly> {
    if s.fly() {
        println!("{:?} can fly", s);
    } else {
        println!("{:?} can;t fly", s);
    }
    Box::new(s)
}

3.4.4 标签trait

trait 对行为约束的特性非常适合作为类型的标签。

rust 一共提供了5个重要的标签 trait,都被定义在标准库 std::marker 模块:

  • Sided trait: 用来表示编译期可确认大小的类型
  • Unsize trait:用来标识动态大小类型(DST),目前该trait为实验特性(TBD:待验证是否有更新)
  • Copy trait:用来标识可以按位复制其值的类型
  • Send trait:用来标识可以跨线程安全通讯的类型
  • Sync trait:用来标识可以在线程间安全共享引用的类型

5.5.3 - Rust中的Trait Object

Rust中的Trait Object

5.5.3.1 - Rust中的Trait Object

Rust中的Trait Object

参考资料

5.6 - Rust的类型转换

Rust的类型转换

类型转换分为隐式类型转换(Implicit Type Conversion)和显式类型转换(Explicit Type Conversion):

  • 隐式类型转换: 由编译期或者解释器来完成,开发者并未参与,所以又称之为强制类型转换(Type Coercion)
  • 显式类型转换: 由开发者指定,即一般意义上的类型转换(Type cast)

5.6.1 - Deref解引用

Rust的Deref解引用

5.6.1.1 - [Rust编程之道笔记]Deref解引用

Rust编程之道一书 3.5.1 Deref解引用

3.5.1 解引用

rust 的隐式类型转换基本上只有自动解引用。自动解引用的目的主要是方便开发者使用智能指针。

自动解引用

自动解引用虽然是编译期来做的,但是自动解引用的行为可以由开发者来定义。

引用使用 & 操作符,而解引用使用 * 操作符。可以通过实现 Deref trait 来自定义解引用操作。

Deref 有一个特性是强制隐式转换,规则是这样:如果有一个类型T实现了 Deref<Target=U> ,则该类型T的引用(或者智能指针)在应用的时候会被自动转换为类型U。

Deref内部实现:

pub trait Deref {
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
}

pub trait DerefMut : Deref {
    fn deref_mut(&mut self) -> &mut Self::Target;
}

DerefMut 和 Deref 类似,不过返回的是可变引用。

实现 Deref 的目的是简化编程,避免开发者自己手工转换。

手动解引用

如果某类型和其解引用目标类型中包含了相同的方法,编译期就不指导该用哪一个了,此时就需要手动解引用。

5.6.2 - as操作符

Rust的as操作符

5.6.2.1 - [Rust编程之道笔记]as操作符

Rust编程之道一书 3.5.2 as操作符

3.5.2 as操作符

as 操作符最常用的场景就是转换 rust 中的基本数据类型。

as 关键字不支持重载。

长类型转换为短类型的时候会被 截断处理。

无歧义完全限定语法

为结构体实现多个trait时,可能会出现同名的方法,使用 as 操作符可以帮助避免歧义。

fn main() {
    let s = S(1);
    // 当 trait 的静态函数来调用
    A::test(&s, 1);
    B::test(&s, 1);
    // 使用as操作符
    <S as A>::test(&s, 1);
    <S as B>::test(&s, 1);
}

类型和子类型相互转换

as转换可以用于类型和子类型之间的转换。

&'static str' 类型是 &'a str' 类型的子类型。两者的生命周期标记不同,'a'static 都是生命周期标记,其中 'a 是泛型标记,是 &str 的通用形式,而 'static 则是特指静态生命周期的 &str 字符串。

fn main() {
    let a: &'static str = "hello"; // 'static str
    let b: &str = a as &str; // &str
    let c: &'static str = b as &'static str; // 'static str
}

可以通过 as 操作符将 &'static str'&'a str' 相互转换。

5.6.3 - From

Rust的From

5.6.3.1 - [Rust编程之道笔记]From Trait

Rust编程之道一书 3.5.3 From Trait

3.5.3 From和Into

From 和 Into 是定义在 std::convert 模块中的trait,定义了 from 和 into 两个方法,互为反操作。

pub trait From<T> {
    fn from(T) -> Self;
}
pub trait Into<T> {
    fn into(self) -> T;
}

Into的默认规则:如果类型 U 实现了 From<T>,则 T 类型实例调用 into 方法时可以转换为类型 U。

这是 rust 标准库中有一个默认的实现:

impl<T, U> Into<U> for T where U: From<T>

tryFrom 和 tryInto trait

是 From 和 Into 的错误处理版本,因为转型转换是有可能发生错误的,所以需要进行错误处理时就可以用 TryFrom 和 TryInto。

AsRef 和 AsMut trait

将值分别转换为不可变引用和可变引用。

5.6.4 - Into

Rust的Into

6 - Rust的所有权机制

Rust的所有权机制

6.1 - [Rust编程之道笔记]所有权

Rust编程之道一书 第5章 所有权

Rust受现代c++的启发,引入智能指针来管理堆内存。在rust中,所有权是系统性的概念,是rust语言的基础设施。

5.1 通用概念

编程语言中的值主要分成两类:

  • 值类型(Value):数据直接存储在栈中的数据类型
  • 引用类型(Reference):将数据存在堆中,而栈中值存放指向堆中数据的地址(指针)

为了更精确的对复杂类型进行描述,引入值语义和引用语义:

  • 值语义(Value Semantic):按位复制以后,和原始对象无关
  • 引用语义(Reference Semantic):也叫指针语义。一般是指将数据存储于对内存中,通过栈内存的指针来管理堆内存的数据,并且引用语义禁止按位复制。

按位复制就是指栈复制,也叫浅复制,只复制栈上的数据。深复制就是对栈上和堆上的数据一起复制。

对于实现Copy trait的类型,其 clone 方法必须是按位复制的。

rust通过 Copy 这个标记 trait 将类型按值语义和引用语义做了精准的分类,帮助编译期检测出潜在的内存安全问题。

引用语义不能实现copy,但是可以实现 Clone 的 clone 方法,以实现深度复制,在需要时可以显示调用。

5.2 所有权机制

在所有权体制下,Rust引入新的语义:

  • 复制(copy):对应值语义。对于可以安全在栈上进行按位复制的类型,就只需要按位复制
  • 移动(move):对应引用语义。对于在堆上存储的数据,无法安全地在栈上进行按位复制。如果堆上的数据不变,只需要在栈上移动指向堆内存的指针地址,不仅保证了内存安全,也拥有与栈复制同样的性能。

一个值的所有权被转移给另外一个变量绑定的过程,就叫做所有权转移。

rust中每个值都有一个所有者,更进一步说就是,rust中分配的每块内存都有其所有者,所有者负责该内存的释放和读写权限,并且每次每个值只能有唯一的所有者,这就是rust的所有权机制(ownership)

所有权的特点

所有者拥有以下三种权限:

  • 控制资源(不仅仅是内存)的释放
  • 出借所有权,包括不可变(共享)的和可变(独占)的
  • 转移所有权

对于可以实现 Copy 的复制语义类型来说,所有权并未改变。对于复合类型来说,是复制还是移动,取决于其成员的类型。

  • 结构体:即使结构体的成员都是复制语义类型,但是rust也不会默认为其实现 Copy。需要手工添加 #[derive(Debug, Copy, Clone)]
  • 枚举:类似结构体
  • 元组:本身实现了 Copy,如果元素均为复制语义类型,则默认是按位复制,否则会执行移动语义。
  • 数组:类似元组
  • Option类型:类似元组

5.3 绑定、作用域和生命周期

Rust 使用 let 关键字来生命变量。let 有 let banding 之意,let 声明的变量实际不是传统意义上的变量,而是指一种绑定语义。let 绑定了标识符和内存,而且使得标识符对那块内存拥有了所有权,因此被称为“绑定”。

5.3.1 不可变与可变

不可变(Immutable)的优点:

  • 多线程并发时,不可变的数据可以安全地在线程间共享
  • 函数的“副作用”可以得到控制

Rust声明的绑定默认不可变,如果需要修改,Rust 提供关键字 mut 来声明可变绑定。

5.3.2 绑定的时间属性-声明周期

变量绑定具有“时空”双重属性:

  • 空间属性:指标识符与内存空间进行了绑定
  • 时间属性:指绑定的时效性,也就是它的生存周期

除了 let 声明外,还有一些场景会创建新的词法作用域:

  • 花括号{}

  • match 匹配

  • 循环语句

  • if let 和 while let 块

  • 函数

    函数参数是复制语义时按位复制,是移动语义时会转移所有权

  • 闭包

    闭包会创建新的作用域,对于环境变量来说有一下三种捕获方式:

    1. 对于复制语义类型,以不可变应用(&T)来捕获
    2. 对于移动语义类型,执行移动语义转移所有权来捕获
    3. 对于可变绑定,如果在闭包中包含对其进行修改的操作,则以可变引用(&mut)来捕获

5.4 所有权借用

引用与借用

引用(Reference)是Rust提供的一种指针语义。应用是基于指针的实现,和指针的区别是:

  • 指针保存的是指向内存的地址
  • 引用可以看做某块内存的别名(alias),使用引用需要满足编译期的各种安全检查规则。

引用分为不可变引用(使用&操作符)和可变引用(使用&mut操作符)。

在所有权系统中,引用&x可称为 x 的借用(Borrowing),通过 & 操作符来完成所有权的租借。借用所有权不会引起变量所有权的转移。

借用所有权会让所有者(owner)受到如下限制:

  • 在不可变借用期间,所有者不能修改资源,并且也不能再进行可变借用
  • 在可变借用期间,所有者不能访问资源,并且也不能再出借所有权

引用在离开作用域之时,就是归还所有权之时。

借用规则

为了保证内存安全,借用必须遵循以下三个规则:

  • 规则一:借用的生命周期不能长于出借方(拥有所有权的对象)的生命周期

  • 规则二:可变借用(引用)不能有别名(Alias,即其他不可变引用),因为可变借用具有独占性

  • 规则三:不可变借用(引用)不能再次出借为可变借用

规则一是为了防止出现悬垂指针,规则二和规则三总结为一条核心原则:共享不可变,可变不共享。规则二和规则三描述的不可变借用和可变借用就相当于内存的读写锁,同一时刻,只能拥有一个写锁,或者多个读锁,不能同时拥有。

Rust的借用检查带来如下好处:

  • 不可变借用保证了没有任何指针可以修改值的内存,便于将值存储在寄存器中
  • 可变借用保证了在写的时候没有任何指针可以读取值的内存,避免了脏读
  • 不可变借用保证了内存不会在读取之后被写入新数据
  • 保证了不可变借用和可变借用不相互依赖,从而可以对读写操作进行自由移动和重新排序

解引用会获得所有权。

5.5 生命周期参数

跨函数使用借用,需要显式地对借用参数或返回值使用生命周期参数进行标注。

5.5.1 显式生命周期参数

生命周期参数必须以单引号开头,参数名通常都是小写字母,比如 'a 。生命周期参数位于引用符号 & 后面,并使用空格来分割生命周期参数和类型。如:

&'a i32;
&'a mut i32;

标注生命周期参数并不能改变任何引用的生命周期长短,它只用于编译器的借用检查,来防止悬垂指针。

函数签名中的生命周期参数

函数签名中的生命周期参数使用如下标注语法:

fn foo<'a>(s: &'a str, t: &'a str) -> &'a str;

函数名后面的 <'a> 为生命周期参数的声明,与范型参数类似,必须先声明才能使用。

  • 输入生命周期(input lifetime):函数或者参数的生命周期
  • 输出生命周期(output lifetime):返回值的生命周期

函数签名的生命周期参数有这样的限制条件:输出(借用方)的生命周期长度必须不长于输入(出借方)的生命周期长度。(遵循借用规则一)

禁止在没有任何输入参数的情况下返回引用。因为明显会造成悬垂指针。

从函数中返回(输出)一个引用,其生命周期参数必须与函数的参数(输入)相匹配,否则,标记生命周期参数毫无意义。

函数生命中的 'a 可以看作是一个生命周期范型参数,输入引用和输出引用都标记为 'a 意味着输出引用(借用方)的生命周期不长于输入引用(出借方)的生命周期。

对于多个输入参数的情况,也可以标注不同的生命周期参数,如:

fn the_longest<'a, 'b: 'a>(s: &'a str, t: &'b str) -> &'a str;

'b: 'a 的意思是范型生命周期参数 'b 的存活时间长于范型生命周期参数 'a (即 'b outlive 'b)。

生命周期参数的目的:生命周期参数是为了帮助借用检查器验证非法借用。函数间传入和返回的借用必须相关联,并且返回的借用生命周期必须比出借方的生命周期短。

生命周期参数是为了帮助借用检查器验证合法的引用,消除悬垂指针。

结构体定义中的生命周期参数

结构体在含有引用类型成员的时候也需要标注生命周期参数。

struct Foo<'a> {
    part: &'a str,
}

这里的生命周期参数标记,实际是和编译器约定了一个规则:结构体实例的生命周期应短于或者等于任意一个成员的生命周期

方法定义中的生命周期参数

需要在 impl 关键字之后申明生命周期参数:

impl<'a> Foo<'a> {
    fun new(s: &'a str) -> Self {
        ......
    }
    ......
}

枚举和结构体对生命周期参数的处理方式是一样的。

静态生命周期参数

字符串字面量是全局静态类型,它的数据和程序代码一起存储于可执行文件的数据段中,其地址在编译器是已知的,无法更改。

在 rust 2018 版本中,使用 const 和 static 定义字符串字面量时,都可以省掉 ‘static 静态生命周期参数。

5.5.2 省略生命周期参数

Rust针对某些场景确定了一些常见的模式,将其编码到Rust编译器中,以便编译器可以自动补齐函数签名中的生命周期参数,这样就可以省略生命周期参数。

被硬编码进编译器的模式被成为生命周期省略规则(lifetime Elision Rule),一共包含三条规则:

  • 每个输入位置上省略的生命周期都将成为一个不同的生命周期参数
  • 如果只有一个输入生命周期的位置(不管是否省略),则该生命周期都将分配给输出生命周期
  • 如果存在多个输入生命周期的位置,但是其中包含找 &self 或者 &mut self,则 self 的生命周期将被分配给输出生命周期。

5.5.3 生命周期限定

生命周期参数可以像 trait 那样作为范型的限定,有以下两种形式:

  1. T: 'a : 表示 T 类型中的任何引用都要"活得" 和 'a 一样长
  2. T: Trait + 'a : 表示 T 类型必须实现 Trait 这个trait,并且T类型中任何引用都要"活得" 和 'a 一样长

5.5.4 trait对象的生命周期

trait 对象和生命周期有默认遵循的规则:

  • trait 对象的生命周期默认是 'static
  • 如果实现 trait 的类型包含 &'a X 或者 &'a mut X,则默认生命周期就是 'a
  • 如果实现 trait 的类型只有 T: 'a,则默认生命周期就是 'a
  • 如果实现 trait 的类型包含多个类似 T: 'a 的从句,则生命周期需要明确指定

5.6 智能指针与所有权

智能指针和普通引用的区别之一就是所有权的不同:

  • 智能指针拥有资源的所有权
  • 普通引用只是对所有权的借用

Box<T> 智能指针可以使用解引用操作符进行解引用。

之所以可以进行解引用,是因为 Box<T> 实现了 deref 方法:

impl<T: ?Sized> Deref for Box<T> {
    type Target = T;
    fn deref(&self) -> &T {
        &**self
    }
}

对于 Box<T> 类型来说,如果包含的类型T属于复制语义,则执行按位复制;如果属于移动语义,则移动所有权。

5.6.1 共享所有权 Rc<T>Weak<T>

Rust 中提供了 Rc<T> 智能指针来支持引用计数。

Rc<T> 可以将多个所有权共享给多个变量。 Rc<T> 主要用于系统共享堆上分配的数据可以供程序的多个部分读取的场景,并且主要确保共享的资源的析构函数都能被调用。

Rc<T> 是单线程引用计数指针,不是线程安全的类型。

  • 强引用: 通过 clone 方法共享的引用所有权被成为强引用
  • 弱引用: 通过 downgrade 方法创建的 Weak<T> 智能指针属于 Rc<T> 的另一种版本,它共享的指针没有所有权,所以被成为弱引用。

5.6.2 内部可变性 Cell<T>RefCell<T>

Rust 中的可变或不可变主要是针对一个变量绑定而言,比如对于结构体来说,可变或者不可变只能对其实例进行设置,而不能设置单个成员的可变性。但是在实际的开发中,某个字段是可变而其他字段不可变的情况是确实存在的。

Rust 提供Cell<T>RefCell<T> 来应对这种情况,他们本事上不属于智能指针,只是提供内部可变性(Interior Mutability)的容器。

Cell<T>

内部可变性实际上是 Rust 中的一种设计模式。

内部可变性容器是对 Struct 的一种封装,表面不可变,但内部可以通过某个方法来改变里面的值。

使用 Cell<T> 内部可变容器确实方便了编程,它提供的 set/get 方法像极了 oop 语言中常见的 getter/setter 方法,封装了对象属性的获取和设置方法。

Cell<T> 通过对外暴露的 get/set 方法实现了对内部值的修改,而其本身却是不可变的。所以,实际上 Cell<T> 包裹的 T 本身合法的避开了借用检查。

对于包裹在 Cell<T> 中的类型 T ,只有实现了 Copy 的类型T,才可以使用 get 方法获取包裹的值,因为 get 方法返回的是对内部值的复制。但是任何类型T都可以使用set方法修改其包裹的值。

对于实现了 Copy 的T,可以任意读取;对于没有实现 Copy 的类型T,则提供了 get_mut 方法来返回可变借用,依然遵循 Rust 的借用检查规则。

Cell<T> 内部每次 get/set 都会执行一次按位复制。

RefCell<T>

对于没有实现 Copy 的类型,使用 Cell<T> 有许多不变。 Rust 提供的 RefCell<T> 适用的范围更广,对类型T并没有 Copy 的限制。

RefCell<T> 提供 borrow/borrow_mut 方法,对应 RefCell<T> 的 get/set 方法。

RefCell<T> 维护一个运行时借用检查器,有运行时开销。

小结

Cell<T>RefCell<T> 使用最多的场景就是配合只读引用来使用,比如 &T 或者 Rc<T>

Cell<T>RefCell<T> 之间的区别可以总结为:

  • Cell<T> 使用 set/get 直接操作包裹的值,而 RefCell<T> 通过 borrow/borrow_mut 返回包装过的引用 Ref<T>RefMut<T> 来操作包裹的值。
  • Cell<T> 一般适合复制语义类型(实现了 Copy), RefCell<T> 一般适合移动语义类型(未实现 Copy)
  • Cell<T> 无运行时开销,而且永远不会在运行时引发 panic 错误。 RefCell<T> 需要在运行时执行借用检查,有运行时开销。而且一旦发现违反借用规则的情况,则会引发看线程 panic 。

5.6.3 写时复制Cow<T>

写时复制(Copy On Write / Cow) 是一种优化策略。

Rust 中的 Cow<T> 是一个枚举体的智能指针,包括两个可选值:

  • Borrowed:用于包裹引用
  • Owned:用于包裹所有者

Cow<T> 提供的功能是: 以不可变的方式访问借用内容,以及在需要可变借用或所有权的时候再克隆一份数据Cow<T> 旨在减少复制操作,提供性能,一般用在读多写少的场景。