Rust 之二 项目管理(Package、Crate、Workspace 等)、文档规范、编码规范

概述

  编程语言发展到今天,除了有语言标准之外,还有一系列其他约定俗成的标准,例如,项目管理、文档规范、编码规范等等。很多早期的编程语言则没有一个统一的标准,而 Rust 编程语言作为一个完全开源的后起之秀,在诞生之初就针对性的解决完善了这些问题!同时又充分吸收了现代编程语言的一些特性,例如,Rust 有前端编程语言标配的包管理器,类似于前端脚手架的项目管理等。

  • Node.js:npm + https://npmjs.org/
  • Python:pip + https://pypi.org/
  • Ruby: RubyGems + https://rubygems.org/
  • Rust:cargo + https://crates.io/

项目管理

  以前比较古老的编程语言通常没有一个固定的项目管理工具,通常都是在发展过程中出现一些第三方的 IDE 作为各自的管理工具。Rust 编程语言作为一个完全开源的后起之秀,在诞生之初就引入了自己的项目管理工具!

管理工具

  Rust 官方并没有提供一个统一的 IDE 作为项目管理的工具,但是,提供了一系列适用于各个平台的开发辅助命令行工具来帮助我们管理、编译 Rust 代码项目以及 Rust 开发环境。

Cargo

  Rust 官方提供了一个名为 Cargo 的命令行项目管理工具,绝大多数情况下,我们都是使用 cargo 命令及 *.toml 的配置文件来管理编译项目。Rust 项目源码的基本结构就是针对 Cargo 这个管理工具来定的!
在这里插入图片描述

其他工具

  此外,Rust 官方还提供了一系列其他工具,例如,代码格式化的 rustfmt、代码检查工具 clippy 等等。详细介绍参见博文 Rust 之一 基本环境搭建、各组件工具的文档、源码、配置

项目结构

  Rust 编程中一个项目通常称为一个 Package,并将 Package 分成多个 Crate, Crate 再进一步分成多个 Module。并规定了如何通过绝对路径或相对路径从一个 Module 导入另一个 Module 中定义的内容的方式。
在这里插入图片描述

  • 一个 Package 中至少包含一个 Crate(可以是一个 Library Crate 也可以是一个 Binary Crate)
  • 一个 Package 中最多只能包含一个 Library Crate
  • 一个 Package 中可以包含任意多个 Binary Crate
  • 一个 Package 会包含一个 Cargo.toml 文件
  • 一个 Crate 可以包含任意多个 Module
  • Module 可以嵌套

  注意,根据传统编程语言的称呼,我们通常会把其他人共享的代码称为包(Package)或库(Library),但 Rust 社区中习惯上都是叫 Crate。因此,在 Rust 中我们平时说的包、库、Crate 很多时候都是一个东西,但实际上他们还是有些区别的!

Package

  Package(中文名 )是 Cargo 进行项目管理的基本单位,它是一个提供了一系列功能的一个或者多个 Crate 的集合。Rust 官方对 Package 的目录结构进行了规范化,其标准的目录结构如下所示:

.
├── Cargo.lock						# 在执行构建后自动生成,固定名字,不能更改
├── Cargo.toml						# 每个 Package 必有一个,固定名字,不能更改
├── src/							# Cargo 要求源文件位于 src 目录中。顶级项目目录仅用于存放 README 文件、许可证信息、配置文件以及与代码无关的其他任何内容。
│   ├── lib.rs						# 这个就是 Library Crate 的入口,名字必须为 lib.rs,这个源码文件称为 Library Cratecrate root。
│   ├── single_file_module.rs		# 这个就是一个 Module,只有一个文件
│   ├── multi-file-module/			# 可以使用一个子目录来组织多个 Module
│   │   ├── mod.rs
│   │   ├── a_module_file.rs
│   │   └── another_module_file.rs
│   ├── main.rs						# 这个就是 Binary Crate,名字必须为 main.rs,被称为 Binary Cratecrate root
│   └── bin/						# 如果有多个 Binary Crate,则必须将他们放到 bin 目录下
│       ├── named-executable.rs		# 这也是个 Binary Crate
│       ├── another-executable.rs	# 这也是个 Binary Crate
│       └── multi-file-executable/	# 可以使用一个子目录来组织 Binary Crate 及其 Module
│           ├── main.rs				# 这也是个 Binary Crate
│           └── some_module.rs		# 这个就是一个 Module
├── benches/						# 固定名字,不能更改。可选,很多 Package 没有
│   ├── large-input.rs
│   └── multi-file-bench/
│       ├── main.rs
│       └── bench_module.rs
├── examples/						# 固定名字,不能更改。可选,很多 Package 没有
│   ├── simple.rs
│   └── multi-file-example/
│       ├── main.rs
│       └── ex_module.rs
├── tests/							# 固定名字,不能更改。可选,很多 Package 没有
│   ├── some-integration-tests.rs
│   ├── common/mod.rs -> shared module for integration tests
│   └── multi-file-test/
│       ├── main.rs
│       └── test_module.rs
└── 其他目录							# 根据自己的需要,可以选择一些其他目录。例如,创建一个 crates 目录存放本地的 Crate
  1. 这个是目录结构实际上是 Rust 官方提供的 Cargo 这个项目管理工具的要求,如果不使用 Cargo,则目录结构可以变动

  2. 最简单的 Package 就只包含一个 Binary Crate 或 Library Crate,因此,在 Rust 中,很多时候一个 Package 就是一个 Crate。使用 cargo new 命令就可以自动生成一个只含一个 Crate 的 Package。

  3. 官方建议使用全小写来作为名字,这是因为 Rust 采用 蛇形命名法(snake_case)串式命名法(kebab-case) 命名规范。
    在这里插入图片描述

  4. Package 的名字不能与 Rust 库内建的同名
    在这里插入图片描述

Cargo.toml

  Cargo.toml 是 Cargo 引入的一个 Manifest 文件,每一个 Package 都必须包含一个 Cargo.toml 文件,它包含编译当前 Package 所需的元数据,用以阐述如何去构建其中的各个 Crate。它是由开发者手动编写的项目的配置清单,描述了项目信息和依赖项等。

# [package] 是当前这个 Package 的配置。[xxx] 这个在 TOML 语法中被称为 表(也被称为哈希表或字典)是键值对的集合。
[package]		
name = "demo"
version = "0.1.0"
edition = "2021"

# [dependencies] 是当前这个 Package 的的依赖。
[dependencies]
rand = "0.8.0"
  • Rust 中的绝大多数工具的默认配置文件都是使用 TOML(Tom’s Obvious Minimal Language)这种类型的配置文件格式。

    • 为一个处理单位,称为 键值对,格式为 键名 = 键值,键名和键值周围的空白会被忽略。
      • 键值的类型如下所示
        • 字符串:多行基本字符串 "xxx" 可以任意多行,包含转义字符等;字面量字符串 'xxxx' 只能一行,不能含转义字符;多行字面量字符串 '''xxxx''' 可以多行,但不能含转义字符
        • 整数:整数是纯数字;正数可以以加号为前缀;负数以减号为前缀。可以在数字之间用下划线来增强可读性,例如 int6 = 5_349_221。带有 0x 前缀的十六进制,带有 0o 前缀的八进制,带有 0b 前缀的二进制
        • 浮点数。小数(3.14)、指数(5e+22)、无穷(+inf、-inf)、非数(+nan、-nan)
        • 布尔值。true 和 fasle
        • 坐标日期时刻
        • 各地日期时刻
        • 各地日期
        • 各地时刻
        • 数组。以方括号包裹,里面的值以逗号风格,例如 [ 1, 2, 3 ]。数组可以跨行
        • 内联表
      • 键名可以是裸的,引号引起来的,或点分隔的。例如,abc = "xxx""abc" == "xxx"a.b.c = 11。需要注意,点分隔形式实际上是表示下面的子节点!
    • # 开头作为注释
    • TOML 是大小写敏感的。
    • TOML 文件必须是合法的 UTF-8 编码的 Unicode 文档。
    • 空白是指制表符(0x09)或空格(0x20),换行是指 LF(0x0A)或 CRLF(0x0D 0x0A)
    • [xxx] 表示后续键值对的集合,在 TOML 语法中被称为表(也被称为哈希表或字典)。[[xxx]] 称为表数组,其中 xxx 是表头,多个 [[xxx]] 组成一个表
  • Cargo.toml 包含如下字段

    • cargo-features: 当前为不稳定特性,仅适用于 Nightly 通道
    • [package]: 定义 Package 的基本信息
      • name: 名字,最少必须有该字段。如果是要发布到 crate.io 的包,则需要额外几个字段,例如 version 字段
        • 必须只使用字母、数字或 -_,并且不能为空
      • version: 版本
        • Rust 使用语义化版本号 x.y.z 格式
      • authors: 作者。以 [xxx, yyy] 的形式列出所有作者,例如,authors = ["zcs", "ZCShou <72115@163.com>"]
      • edition: Rust 语言版本
      • rust-version: 最小支持的 Rust 版本。第一个支持这个字段的 Cargo 版本是在 Rust 1.56.0中发布的。在较旧的版本中,该字段将被忽略,并且 Cargo 将显示一个警告。
      • description: 描述
      • documentation: 文档 URL
      • readme: README 文件的路径
      • homepage: 主页 URL
      • repository: 源码仓库 URL
      • license: 包的 license.
      • license-file: 许可证文本的路径
      • keywords: 关键字
      • categories: 包的分类
      • workspace: 包的工作区路径
      • build: 包生成脚本的路径
      • links: 包链接到的本机库的名称
      • exclude: 发布时要排除的文件。以数组形式给出,例如 exclude = ["/ci", "images/", ".*"]
      • include: 发布时要包括的文件。以数组形式给出,例如 include = ["/src", "COPYRIGHT", "/examples", "!/examples/big_example"]
      • publish: 可用于防止发布包
      • metadata: 外部工具的额外设置
      • default-run: 执行 cargo run 时默认要使用的 Crate
      • autobins: 禁用二进制自动发现
      • autoexamples: 禁用示例自动发现
      • autotests: 禁用测试自动发现
      • autobenches: 禁用基准自动发现
      • resolver: 设置要使用的依赖项解析器
    • Target tables:
      • [lib]: 库目标设置
      • [[bin]]: 二进制目标设置
      • [[example]]: 目标设置示例
      • [[test]]: 测试目标设置
      • [[bench]]: 基准目标设置
    • Dependency tables:
      • [dependencies]: 包库依赖项
      • [dev-dependencies]: 示例、测试和基准的依赖项
      • [build-dependencies]:构建脚本的依赖项
      • [target]: 特定于平台的依赖项
      • [badges]: 要在注册表中显示的徽章
      • [features]: 条件编译特性
      • [lints]: 配置 Lint
      • [patch]: 重写依赖项。
      • [replace]: 重写依赖项(已弃用)
      • [profile]: 编译器设置和优化
      • [workspace]: 工作区定义
  • 版本号采用语义化版本(Semantic Versioning)(有时也称为 SemVer),这是一种定义版本号的标准。

  • Cargo 会根据这个文件中的 [dependencies] 从 registry 上获取所有包的最新版本信息放到 $HOME/.cargo/ 目录中

Cargo.lock

  Cargo.lock 文件中包含了 Cargo.toml[dependencies] 中给出的依赖的精确描述信息,它是一个确保任何人在任何时候重新构建代码,都会产生相同的结果一个实现方法。Cargo.lock 是由 Cargo 自动生成并维护,因此不要去手动修改。

  当第一次构建项目时,Cargo 将根据 Cargo.toml 中用户给出的信息计算出所有符合要求的依赖版本并写入 Cargo.lock 文件。当将来构建项目时,Cargo 会发现 Cargo.lock 已存在并使用其中指定的版本,而不是再次计算所有的版本。

  1. 由于 Cargo.lock 文件对于可重复构建非常重要,因此它通常会和项目中的其余代码一样纳入到版本控制系统中。
  2. 当确实需要升级 crate 时,Cargo 提供了 cargo update 这个命令,它会忽略 Cargo.lock 文件,并计算出所有符合 Cargo.toml 声明的最新版本。

Crate

  Crate 是 Rust 编译器一次处理的最小的单位,它既可以是一个库,也可以是一个可执行程序,分别被称为 Library Crate 和 Binary Crate。其中,Library Crate 的默认入口是 lib.rs 文件,他们不能被编译成可执行文件,也没有 main() 函数,而 Binary Crate 的默认入口则是 main.rs 文件的 main() 函数,它会被变为一个可执行程序。

  1. 使用 cargo new --bin pacakge_name 命令可以创建一个只包含 src/main.rs 的 Package,其中只包含一个名字与 Package 相同 Crate,这个 Crate 就是一个 Binary Crate。其中 --bin 可以省略,默认就是 Binary Crate。
    在这里插入图片描述
  2. 使用 cargo new --lib pacakge_name 命令可以创建一个只包含 src/lib.rs 的 Package,其中只包含一个名字与 Package 相同 Crate,这个 Crate 就是一个 Library Crate
    在这里插入图片描述
  3. 使用 cargo install 命令可以从 crate.io 上下载 Binary Crate 并在本地安装和使用。只有拥有二进制目标文件的包能够被安装,并会被安装到 $HOME/.cargo/bin
crate root

  crate root 是一个源码文件,Rust 编译器以它为入口点,自动查找并编译其中使用的其他源码文件。在 Cargo 中 Library Crate 的 crate root 固定为 src/lib.rs,而 Binary Crate 的 crate root 则固定为 src/main.rs

crates.io

  crates.io 是 Rust 官方提供的一个在线共享 crates 的平台,实际上就是 Rust 的一些 Package,就类似于一些前端语言的包共享平台(例如 Node.js 的 https://npmjs.org/)。这个平台就是用户通过 cargo 使用的包的来源,国外通常将类似平台称为 Package Registry

  1. crates.io 的全套源代码托管在了 https://github.com/rust-lang/crates.io 代码仓库中
发布自己的 crate

  任何人都可以将自己编写的 crate 上传供其他人使用!首先需要在 https://crates.io 上注册账号(现在仅支持使用 Github 登陆),然后在 https://crates.io/settings/tokens 页面中创建一个 API token。
在这里插入图片描述
  拿到 API token 之后,需要使用 cargo login 自己的 API token 命令登陆到 https://crates.io,以便进行后续的操作。(这个命令会告知 Cargo 你的 API token 并将其储存在本地的 ~/.cargo/credentials 文件中)

  1. 准备自己要共享的 Crate。在发布之前,确保 Cargo.toml 中的 [package] 表中以下字段已经被设置

    [package]
    name = "guessing_game"
    version = "0.1.0"
    edition = "2021"
    authors = "ZCShou"
    categories = ["development-tools::procedural-macro-helpers", "parser-implementations"]
    description = "A fun game where you guess what number the computer has chosen."
    license = "MIT OR Apache-2.0"
    repository = "https://github.com/zcshou/xxx"
    documentation = "https://github.com/zcshou/"
    
    [dependencies]
    
    • nameversionedition 其中的 name 需要一个唯一的名称,不能与 crates.io 上已有的重复
    • license 指名使用的协议,协议名字参见 https://spdx.org/licenses/,多个时使用 OR 间隔开,例如 Rust 自己使用的是 license = "MIT OR Apache-2.0"。或者使用 license-file 指出自己的协议源文件,自己的协议源文件需要放到 Crate 中作为一部分
    • description 描述自己的 Crate 的基本信息
    • documentation 在线文档的网站。这就要求我们的 Crate 有比较友好的文档,文档的书写就是后文介绍的 rustdoc 工具生成的文档
    • repository 源码仓库
    • authors 作者
    • categories 分类
  2. 使用 cargo package 命令对项目进行一些验证,然后将源代码压缩到 .crate 文件中

  3. 使用 cargo publish 就会自动发布到 crates.io 上。

    • 发布 crate 是上传特定版本的 crate 到 crates.io 以供他人使用,并且每个版本都不能被删除!
    • 当修改了 crate 并准备好发布新版本时,改变 Cargo.toml 中 version 所指定的值,再次执行 cargo publish 即可
    • 如果你要发布包到 crates.io 上,那该包的依赖也必须在 crates.io 上
    • 虽然不能删除之前版本的 crate,但是支持通过 cargo yank --vers 1.0.1 来弃用,从而防止别人使用

  在 crates.io 上分享的包可以是 Library Crate,也可以是 Binary Crate,但是无论哪种都是分享的源码。前者我们可以在自己的项目中的 [dependencies] 中来使用,而后者则可以通过 cargo install xxx 安装(下载源码编译成可执行程序)到本地(默认 $HOME/.cargo/bin)来作为工具使用!

Module

  Module(中文 模块)是代码的组织单元,让我们可以将一个 crate 中的代码进行分组,以提高可读性与重用性。Module 还可以控制项的私有性,即其中的各项内容是可以被外部代码使用的(public),还是作为一个内部实现的内容,不能被外部代码使用(private)。

  1. Module 可以嵌套

  2. Module 的中的内容可以是函数,也可以是结构体、枚举、常量、trait 等

  3. Module 和其他数据类型共享相同的命名空间,因此,模块名字不能和其他类型定义的名字重复

同文件中定义

  Rust 规定可以在同一源码文件中使用 mod 关键字来定义一个 Module,格式为 mod module-name { }
在这里插入图片描述

独立源文件定义

  可以将 Module 代码放到一个单独的源码文件中,而源文件名就是 Module 的名字
在这里插入图片描述

子目录中定义

  可以进一步将独立源文件放到一个子目录中,子目录中的每个独立源文件都是一个 Module。Rust 对这个子目录的格式有强制要求。

mod.rs

  最初的 Rust(Rust 2015) 规定,如果将 Module 放到一个单独的子目录中的时候,就必须在子目录中定义一个 mod.rs 源码文件,否则,Rust 无法将子目录识别为一个 Module。而 mod.rs 中负责导出当前子目录的各个 Module。
在这里插入图片描述

  1. 子目录的名字就是 Module 的名字

  2. mod.rs 中除了重导出其他 Module 外,还可以与普通源码文件一样,定义各种函数、结构体等等

  3. 子目录可以嵌套该结构,每一级目录中都添加一个 mod.rs

<folder_name>.rs

  从 Rust 1.30 版本(Rust 2018)开始,Rust 新增通过在与 Module 子目录同级的目录中定义一个与子目录同名的 <folder_name>.rs 源文件来定义一个 Module 的方式。而 <folder_name>.rs 中就是原 mod.rs 的内容(导出子目录中的 Module)。
在这里插入图片描述

  1. 如果使用了 <folder_name>.rs 就不能在子目录下添加 mod.rs 了,否则编译报错 error[E0761]: file for module utils found at both "src\utils.rs" and "src\utils\mod.rs"

  2. 子目录如果有多个,就需要多个 <folder_name>.rs,与子目录一一对应

  3. 子目录可以嵌套该结构,每一级目录中都添加一个当面子目录同名的 <folder_name>.rs

  4. 可以混用 mod.rs<folder_name>.rs 这两种方式,不过不建议!

声明 Module

  Rust 规定,要使用在独立源文件或者子目录中定义的 Module 时,必须先显式的在将 Module 导入到当前源文件。导入的方式就是使用 mod module_filename; 来声明。Module 必须从 main.rs/lib.rs 开始在模块树中逐级声明。
在这里插入图片描述

  • 同文件中通过 mod module_name { } 定义的 Module 无需显式的声明 Module

  • 同级 Module(例如上图中的 hostingserving)不能直接通过 mod 互相声明,必须由它们的共同父模块(如上图的 front_of_house.rs)统一声明。如上图中 front_of_house 又在 mainmod front_of_house,从而形成 mainmod front_of_househostingserving

  • 如果模块是嵌套子目录则必须需要逐级声明。如上图中 check 先在 front_of_housepub mod check;,而 front_of_house 又在 mainmod front_of_house,从而形成 mainmod front_of_housecheckfoo

Module Tree

  Rust 中实际上是使用 Module Tree 这种数据结构来组织所有 Module 之中的所有内容,Module 声明的规定也是为了符合这个 Module Tree 结构。而 Module 之间的互相使用就以此为基础。以上面图示的 front_of_house 为例对应的 Module Tree 如下所示:

crate
 └── front_of_house
     ├── hosting
     │   ├── add_to_waitlist
     │   └── seat_at_table
     └── serving
         ├── take_order
         ├── serve_order
         └── take_payment

   一个单独的文件或文件夹就是一个独立的 Module,Module 的名字就是源码文件的名字或文件夹名字,被称为隐式模块。其中 src/main.rssrc/lib.rs 比较特殊,两个文件中的内容会构成名为 crate 的 Module,并作为 Module Tree 的根节点。这也是为什么 src/main.rssrc/lib.rs 称为 crate root 的原因。

  • 如果一个模块 A 被包含在模块 B 中,我们将模块 A 称为模块 B 的子(child),模块 B 则是模块 A 的 父(parent)。例如上面的 front_of_house 与 hosting

  • 处于同一级的 模块 A 和模块 B 则互为 兄弟(siblings),例如上面的 hosting 和 serving

  • 所有模块都是名为 crate 的隐藏模块的子(child)模块

Module Path

  Rust 中规定我们需要通过 Module Path 来索引使用 Module 的内容。对应于上面 Module Tree 这个数据结构,每一个要使用的 Module 及 Module 中的内容都是 Module Tree 的一个节点,每个节点就对应了一个唯一的路径,这个路径在 Rust 中就被称为 Module Path。

  • :: 作为分隔符来间隔不同的节点

  实际上,Module Path 这个概念就是一个类似 Linux 中文件系统的管理方式,Rust 中提供了如下三个路径关键字来消除路径歧义,并防止不必要的路径硬编码。

  • crate:表示当前 Package 的根目录,相当于 Linux 文件系统中的 /
  • super:表示了当前位置的父级,相当于 Linux 文件系统中的 ..
  • self:表示了当前位置,相当于 Linux 文件系统中的 .
使用 Module 内容

  使用 Module 内容必须显式通过上面 Module Path 这个结构来作为索引方法。第一种方法是可以选择从 crate 这个根节点开始访问,称为 绝对路径(absolute path) 法;第二种方法则是可以以 selfsuper 或当前模块的标识符开始访问,称为 相对路径(relative path) 法。
在这里插入图片描述

  1. 要使用 Module 内容,则 Module 必须已经被显式的声明了
pub 关键字

  Rust 中默认所有项(函数、方法、结构体、枚举、模块和常量)都是私有的。无论是绝对路径(absolute path) 法还是相对路径(relative path),都只能访问模块对外公开的程序项,而对程序项公开的方法就是在要公开的项前面添加 pub 关键字。

  1. 父模块中的项不能使用子模块中的私有项,但是子模块中的项可以使用它们父模块中的项。

  2. 同级的各个成员(例如兄弟模块之间)可以可以互相引用,而无需 pub 关键字

  3. 模块公有并不使其内容也是公有的。模块上的 pub 关键字只允许其父模块引用它,而不允许访问内部代码。当然仅公开模块通常没啥用,还需要同时我们根据需要公开一些模块中的内容

  4. 在一个结构体定义的前面使用了 pub ,这个结构体会变成公有的,但是这个结构体内的字段仍然是私有的

  5. 如果将枚举设为公有,则它的所有成员都将变为公有

super 关键字

  super 可以用于在当前模块中访问父级模块中的内容,而无需 pub 关键字。但是,访问父级的子模块中的内容时,子模块中的内容本身必须为 pub 公开的。例如,嵌套的 Module 互相调用对的的函数时就可以使用 super 关键字。
在这里插入图片描述

self 则表示当前作用域,类似于 C++ 的 this 指针

use 关键字

  直接使用 Module 会导致我们代码里包含大量引用路径。使用 use 关键字可以将 Module 引入到当前作用域,use 后面的路径可以是绝对路径也可以是相对路径,从而简化对于 Module 中内容的使用的路径。
在这里插入图片描述

  1. 使用 use 引入函数时,习惯是导入到它的父级,而引入结构体、枚举和其他项时,习惯是指定它们的完整路径。

    1. 例如上面示例中 use crate::front_of_house::hosting; 而不是 use crate::front_of_house::hosting::add_to_waitlist;
    2. 如果上面示例中使用枚举时直接是 use crate::front_of_house::MealType;
  2. use 关键字可以使用嵌套路径来消除大量的 use 行,格式 use xx::yy::{zz, ss, ...};。例如,use std::io::{self, Write}; 就相当于use std::io; + use std::io::Write;

  3. 通过 glob 运算符 * 将所有的公有定义引入作用域。例如,use std::collections::*; 将 std::collections 中定义的所有公有项引入当前作用域。

  4. use 可以搭配 as 来重定义模块的名字,例如 use std::io::Result as IoResult;

  5. 当使用 use 关键字将一个名称导入当前作用域时,导入的名称只能在当前作用域使用(相当于当前作用的私有内容)。

重导出

  重导出(Re-exports)允许我们有选择的公开模块中的内容,顺带还可以缩短 Module Path。对于一个稍微复杂点的库来说,通常需要有选择的导出其中的项目给其他调用者使用,重导出就可以实现该目的,实现方法就是使用 pub use 导出指定的内容。

  假设有个名为 lib 的库,内容如下:

pub mod sub_module1 {
    pub struct Foo;
}
pub mod sub_module2 {
    pub struct AnotherFoo;
}

  那么用户可以在自己的源码文件中导入库中的 FooAnotherFoo 这两个函数来使用

use lib::sub_module1::Foo;
use lib::sub_module2::AnotherFoo;

  如果不希望将 sub_module1sub_module2 暴露给用户,则可以添加重导出

// `sub_module1` and `sub_module2` are not visible outside.
mod sub_module1 {
    pub struct Foo;
}
mod sub_module2 {
    pub struct AnotherFoo;
}
// We re-export both types:
pub use crate::sub_module1::Foo;
pub use crate::sub_module2::AnotherFoo;

  这样用户就可以这样 use lib::{Foo, AnotherFoo}; 导入 FooAnotherFoo 这两个函数来使用

Workspace

  Rust 中的一个工作空间是由多个 package 组成的集合,它们共享同一个 Cargo.lock 文件、输出目录和一些设置。组成工作空间的 packages 被称之为工作空间的成员。各成员的类型没有硬性要求,可以有任意多个 Binary Crate 或 Library Crate,也可以是示例(examples/)、测试(test/)、基准测试(benches/)。

  • Cargo 并不假设工作区中的各 Crate 会相互依赖,所以我们需要明确依赖关系

  • Workspace 中的所有的 Package 共同编译到根目录下的 target 目录中

  • 工作区顶层只有一个 Cargo.lock 文件,而不是每个 Package 的目录中都有一个 Cargo.lock。当在不同的 Package 中包含了相同的依赖,则他们会被解析为一个放到 Cargo.lock

  • 即使 Workspace 中包含了某个依赖,不同的 Package 仍要在自己的 Cargo.toml 中进行显示声明为依赖

  • 工作区至少必须有一个成员,要么是根包,要么是虚拟清单

  • 注意,默认使用 Cargo 创建的 Package 都会有 .git 作为版本控制,根据自己的需要删除其中的 .git,一般一个 Workspace 用顶层一个版本控制即可

root package

  若一个 Workspace 的 Cargo.toml 中包含了 [package] 的同时又包含了 [workspace] 部分,此时的 Workspace 本身也是一个 Package,它被称为 Root Package。一个工作空间的根( root )是该工作空间的 Cargo.toml 文件所在的目录。
在这里插入图片描述

  • 此时的 Workspace 的目录结构必须符合上文介绍的 Package 的目录结构(必须有 src/main.rssrc/lib.rs

  • 编译后,通常只会编译 Workspace 这个 Package 本身,其中的其他 Package 除非被 Workspace 这个 Package 依赖,否则不会编译

  • 依赖必须是 Library Crate,如果有多个可执行程序的 Crate,需要编译时必须收到在 Cargo.toml 中用 [[bin]] 指定,或者,可以使用 cargo build --workspace 强制编译所有内容
    在这里插入图片描述

virtual manifest

  若一个 Workspace 的 Cargo.toml 中有 [workspace] 但是没有 [package] 部分,则它是虚拟清单(virtual manifest)类型的工作空间。对于没有主 Package 的场景或希望将所有的 package 组织在单独的目录中时,这种方式就非常适合。
在这里插入图片描述

  • 就是把 workspace 当作组织多个 crate 的容器来使用

  • 此时的 workspace 根目录必须有的只有 Cargo.toml 即可,无需符合上文的 Package 的目录结构!

  • 不允许在虚拟清单类型的工作空间中的 Cargo.toml 存在 [dependencies] 字段,因为它不是一个 Package!

  • 编译后,根据其中 Package 类型的不同,会在根目录的 target 目录下生成不同的可执行文件
    在这里插入图片描述

  • 必须指定 resolver 因为它们没有 package.edition 用来推断解析器版本。

[workspace]

  Cargo 并没有直接提供一个命令来创建一个工作空间(workspace),而是需要我们手动在根目中创建一个 Cargo.toml,然后在该 Cargo.toml 中添加一个 [workspace] 字段。
在这里插入图片描述

resolver

  resolver 用于指定当前 Workspace 使用的依赖解析器版本,目前有 版本1,版本2 和 版本 3 这个三个可选。解析器是一个针对工作区的全局设置,该设置在依赖项中会被忽略。该设置仅对工作区的顶级包生效。如果使用的是虚拟工作区,必须显示在 [workspace] 定义中显式设置 resolver 字段,才能选择启用新的解析器。

  • Edition 2024 依赖解析器默认是版本 3
  • Edition 2021 依赖解析器默认是版本 2
  • Edition 2015、Edition 2018 依赖解析器默认是版本 2

  自 Rust 1.51.0 以来,Cargo 支持一种新的功能解析器,该解析器可以通过在 Cargo.toml 中设置 resolver = "x" 来启用。从 Rust 2021 开始,这将成为默认设置。也就是说,在 Cargo.toml 中写入 edition = "2021" 将自动包含 resolver = "2"

members / exclude

  members 和 exclude 字段共同定义哪些包是工作区的成员。其中,members 指定了要包含在工作区中的包,exclude 指定了要从工作区中排除的包。

  • 后续我们通过 cargo new xxx 时会自动在 Cargo.toml 中添加到 members 中
    在这里插入图片描述
[dependencies]

  单独的 [dependencies] 是用于指定 Package 的依赖的,对于 Workspace,需要通过 [workspace.dependencies] 来定义当前 Workspace 中的所有成员可以共享的依赖!

[package]

  单独的 [package] 是用于指定 Package 的基本信息的,对于 Workspace,需要通过 [workspace.package] 来定义当前 Workspace 中的所有成员可以共享的 package 信息!

[lints]

  单独的 [lints] 是用于指定 Package 的 lints 信息,对于 Workspace,需要通过 [workspace.lints] 来定义当前 Workspace 中的所有成员可以共享的 lints 信息!

其他
  • default-members — 当没有选择特定的包时要操作的包
  • metadata — Extra settings for external tools.

[patch] / [replace]

  • [patch][replace] 指定了覆盖依赖项,不过 [replace] 已弃用

[profile]

  [profile] 用来进行编译器设置和优化

使用第三方包

  Rust 编程可以使用第三方的包,很多人将常用的一些功能编写为了一个库供其他人使用,Rust 官方的共享平台是 crates.io。因此,当我们编写 Rust 代码时,可以先在 crates.io 查找是否有可用的第三方库来简化我们的实现。当然,第三包的分享平台并不局限于 crates.io,很多包在 Github 上提供源码!

  Rust 的项目管理工具 Cargo 同时还是一个软件包的管理工具,当我们在自己的项目中使用了其他软件包时,Cargo 就会自动帮助我们下载我们依赖的软件包,并自动帮我们编译到自己的项目中!

[dependencies]

  要使用第三方包,需要在自己的项目的 Cargo.toml 文件中的 [dependency] 下列出依赖的包。Rust 支持多种方式来引入第三方包,具体如下:

  1. 直接以 包名 = 版本 手动列出依赖包,则 Cargo 默认会从 crates.io (可以通过 Cargo 的配置文件更改包的源地址)下载我们的依赖包。

    • 还可以使用 cargo add 名字@版本 来增加包
    • 使用 cargo update 名字@版本 更新某个包
  2. 可以直接引入 git 仓库作为依赖包。可以使用 rev = "HASH 值"tag = " TAG 名"branch = "分支名" 来指定想要拉取的版本。如果不指定版本,Cargo 会假定我们使用 master 或 main 分支的最新 commit

    [dependencies]
    regex = { git = "https://github.com/rust-lang/regex", branch = "next" }
    
  3. 通过路径引入本地依赖包

    [dependencies]
    hello_utils = { path = "hello_utils" }
    # 以下路径也可以
    # hello_utils = { path = "./hello_utils" }
    # hello_utils = { path = "../hello_world/hello_utils" }
    
  4. 可以根据特定的平台来引入依赖,语法跟 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" }
    
    [target.'cfg(not(unix))'.dependencies]
    openssl = "1.0.1"
    

[dev-dependencies]

  对于只在测试时需要的依赖库需要添加到 Cargo.toml 文件中的 [dev-dependencies] 分组下面,其中的依赖只会在运行测试、示例和 benchmark 时才会被引入。当然,还可以指定平台特定的测试依赖包。

[dev-dependencies]
tempdir = "0.3"

[target.'cfg(unix)'.dev-dependencies]
mio = "0.0.1"

[build-dependencies]

  构建脚本 build.rs 使用的依赖需要放到 Cargo.toml 文件中的 [build-dependencies] 分组下面,同样,还可以指定平台特定的测试依赖包。构建脚本( build.rs )和项目的正常代码是彼此独立,因此它们的依赖不能互通。

[build-dependencies]
cc = "1.0.3"

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

版本兼容

  Rust 本身以及编程中使用的其他软件包,都涉及到不同版本兼容的问题。前面说了,Rust 使用语义化版本号(semver)格式的版本,格式为 x.y.z 的形式,其中 x 被称为主版本号 major,y 被称为次版本 minor,而 z 被称为补丁号 patch。可以看出从左到右,版本的影响范围逐步降低,Rust 的版本兼容策略就是以此为基础。

  • Cargo 认为 0.0.z 是非常原始的版本和其它任何版本都不兼容
  • Cargo 认为 0.y.z(y 非零) 的版本只能与相同 y 值的版本兼容,例如,0.6.30.6.1 兼容,但是 0.7.10.6.1 不兼容
  • Cargo 认为 1.y.z 及之后的版本与所有主版本号相同的版本兼容,例如,2.17.992.0.1 兼容,但是 3.0.12.0.1 不兼容

直接版本号

  当我们要指定包的版本号时,通常是直接给出具体的版本号,例如,rand = "0.8.5",然而,Cargo 却不一定会使用我们指定的版本,而是自动尝试使用它认为的与之兼容的最新的版本。我们可以通过添加一些特殊符号来进行限定!

^ 指定版本

  除了直接给出版本号,还可以在版本号之前添加 ^ 来指定一个版本号范围,然后 Cargo 会使用该范围内的最大版本号来引用对应的包。 实际上我们直接写版本号就等同于用 ^ 指定版本(因为 ^ 是可以省略的),例如,rand = "1.2.3" 就等同于 rand = "^1.2.3"

^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

~ 指定版本

  ~ 指定了兼容版本的最小化版本,根据指定的主版本号、次版本号、补丁号的不同,限制的范围不同。

~1.2.3  等价于 >=1.2.3, <1.3.0  指定主要版本、次要版本和补丁版本,仅允许进行补丁级别的兼容
~1.2    等价于 >=1.2.0, <1.3.0  仅指定主要版本和次要版本,仅允许进行补丁级别的兼容
~1      等价于 >=1.0.0, <2.0.0  仅指定主要版本,则允许进行次要和补丁级别

* 通配符

  使用 * 这种方式允许将 * 所在的位置替换成任何数字。

*     等价于 >=0.0.0 			注意 crates.io 不允许裸 * 版本
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

文档规范

  文档一直都是一个项目最为关键但是很麻烦的工作。Rust 官方专门为文档的编写制定了相关规范,并以此编写了 Rust 相关的各种文档。为此,Rust 官方还提供了一个名为 mdBook 的文档处理工具,以方便编写独立文档,同时还提供了一个名为 rustdoc 的文档处理工具,用于将源码中的注释直接生成文档。

mdBook

  mdBook 是一个将一系列 Markdown 文档(.md)创建为一套 HTML 文档的命令行工具。它是创建产品或 API 文档、教程、课程材料或任何需要简洁、易于导航和可定制的演示文稿的理想工具。Rust 提供的绝大多数文档都是使用 mdBook 来构建的,汇总如下:

名称在线地址源码地址
The Rust Programming Languagehttps://doc.rust-lang.org/book/title-page.htmlhttps://github.com/rust-lang/book
The Rust Edition Guidehttps://doc.rust-lang.org/edition-guide/index.htmlhttps://github.com/rust-lang/edition-guide
The Cargo Bookhttps://doc.rust-lang.org/cargo/index.htmlhttps://github.com/rust-lang/cargo/tree/master/src/doc/src
The rustdoc bookhttps://doc.rust-lang.org/rustdoc/index.htmlhttps://github.com/rust-lang/rust/tree/master/src/doc/rustdoc
The rustc bookhttps://doc.rust-lang.org/rustc/https://github.com/rust-lang/rust/tree/master/src/doc/rustc
The rustup bookhttps://rust-lang.github.io/rustup/index.htmlhttps://github.com/rust-lang/rustup/tree/master/doc/user-guide
The Rust Unstable Bookhttps://doc.rust-lang.org/stable/unstable-book/the-unstable-book.htmlhttps://github.com/rust-lang/rust/tree/master/src/doc/unstable-book
The Embedded Rust Bookhttps://docs.rust-embedded.org/book/https://github.com/rust-embedded/book
The Rust Style Guidehttps://doc.rust-lang.org/nightly/style-guide/https://github.com/rust-lang/rust/tree/HEAD/src/doc/style-guide/
The Rustonomiconhttps://doc.rust-lang.org/nomicon/https://github.com/rust-lang/nomicon
The Rust Referencehttps://doc.rust-lang.org/nightly/reference/https://github.com/rust-lang/reference/
Rust By Examplehttps://doc.rust-lang.org/stable/rust-by-example/https://github.com/rust-lang/rust-by-example
Rust Compiler Development Guidehttps://rustc-dev-guide.rust-lang.org/https://github.com/rust-lang/rustc-dev-guide
Rust Forgehttps://forge.rust-lang.org/https://github.com/rust-lang/rust-forge
Standard library developers Guidehttps://std-dev-guide.rust-lang.org/about.htmlhttps://github.com/rust-lang/std-dev-guide
The Rust Language Design Teamhttps://lang-team.rust-lang.org/welcome.htmlhttps://github.com/rust-lang/lang-team
The Types Teamhttps://rust-lang.github.io/types-team/https://github.com/rust-lang/types-team
wg-asynchttps://rust-lang.github.io/wg-async/https://github.com/rust-lang/wg-async
Asynchronous Programming in Rusthttps://rust-lang.github.io/async-book/https://github.com/rust-lang/async-book
Clippy Documentationhttps://github.com/rust-lang/rust-clippy/tree/master/bookhttps://github.com/rust-lang/rust-clippy/tree/master/book
mdBook Documentationhttps://rust-lang.github.io/mdBook/https://github.com/rust-lang/mdBook/tree/master/guide
Criterion.rs Documentationhttps://bheisler.github.io/criterion.rs/book/https://github.com/bheisler/criterion.rs
Error codes indexhttps://doc.rust-lang.org/stable/error_codes/error-index.htmlhttps://github.com/rust-lang/rust/
Poloniushttps://doc.rust-lang.org/stable/error_codes/error-index.htmlhttps://github.com/rust-lang/polonius
Rust API Guidelineshttps://rust-lang.github.io/api-guidelines/https://github.com/rust-lang/api-guidelines
The Rust RFC Bookhttps://rust-lang.github.io/rfcs/https://github.com/rust-lang/rfcs
The Chalk Bookhttps://rust-lang.github.io/chalk/book/https://github.com/rust-lang/chalk
The bindgen User Guidehttps://rust-lang.github.io/rust-bindgen/https://github.com/rust-lang/rust-bindgen

关于 mdBook 的详细介绍,参见博文 Rust 之一 基本环境搭建、各组件工具的文档、源码、配置

rustdoc

  rustdoc 是 Rust 官方的文档处理工具,并附带 Rust 编译工具链发布,这个工具主要是用来自动收集当前项目源码文件中的各种注释(有特殊格式要求,见下文)来生成对应文档,

  它接受一个 crate root 文件(也支持一个 markdown 文件)作为参数,然后生成 HTML,CSS 和 JavaScript 文件等组成一套静态网页文档。如下官方文档就是使用 rustdoc 生成的:

名称在线地址源码地址
The Rust Standard Libraryhttps://doc.rust-lang.org/std/index.html<>
The Rust Standard Libraryhttps://doc.rust-lang.org/core/index.html<>
The Rust Standard Libraryhttps://doc.rust-lang.org/core/index.html<>

基本格式

  rustdoc 会提取源码文件中特定格式的注释(官方称为文档注释)来生成文档,rustdoc 规定了用于生成的文档的注释必须以 /// 或者 //! 开头,并且注释的内容主要使用 Markdown 语法来进行编写。

  1. /// 用于放在函数、结构体、变量等代码前面以对代码进行注释,称为 outer documentation,示例如下:
    在这里插入图片描述
  2. //! 用于放到一个源码文件的开头给一个源码文件注释,称为 inner documentation。示例如下:
    在这里插入图片描述
    1. 当它位于 root crate 文件时,那么它就是文档主页的注释
    2. 其最后面需要加一个空行
  3. 可以在自己的 src/lib.rsmain.rs 的开头添加 #![warn(missing_docs)],而如果是一个要共享的库则可添加 #![deny(missing_docs)]
  4. 使用 #[doc(hidden)] 可以屏蔽将之后的内容提取到文档中

编码规范

  代码的命名规范、风格、注释方式一直都是程序员比较头疼的一个问题,因为不同的人总有自己的想法和风格。Rust 官方则对 Rust 编程制定了一系列的编码规范,并提供了 rustfmt 这个代码格式化工具来帮助程序员处理这些问题。Rust 语言社区内其实分散着很多编码规范:

命名规范

  Rust 倾向于在“类型”级的结构中使用大驼峰( UpperCamelCase) 命名风格,在 “变量、值(实例)、函数名”等结构中使用蛇形( snake_case)命名风格。针对不同的类型,Rust 官方建议的命名方式如下表所示:

类型命名规则示例
Crates / Packagesnake_casecook.rs make_bin.rs
Modulessnake_caseMod cook {
  …
}
TypesUpperCamelCase
TraitsUpperCamelCase
Enum variantsUpperCamelCaseenum WebEvent {
  PageLoad,
  PageUnload,
  KeyPress(char),
  Paste(String),
  Click { x: i64, y: i64 },
}
Functionssnake_casefn calc_crc() {
  …
}
Methodssnake_case
General constructorsnew or with_more_details
Conversion constructorsfrom_some_other_type
Macrossnake_case!macro_rules! macro_name {
  // 宏规则
  ($arg1:pat, $arg2:expr) => {
    // 生成的代码
  };
}
使用时 macro_name!() 来使用
Local variables(包括函数的形参和实参)snake_caselet first_name = “zcs”
StaticsSCREAMING_SNAKE_CASEstatic FIRST_NAME : &str = “zcs”;
ConstantsSCREAMING_SNAKE_CASEconst FIRST_NAME : &str = “zcs”
Type parametersconcise UpperCamelCase, usually single uppercase letter: T
Lifetimesshort lowercase, usually a single letter: 'a, 'de, 'src
Featuressnake_case
  1. 通常使用 动词-宾语[-error] 这种结构。例如,标准库中有 JoinPathsErrorParseBoolError

  2. 在 UpperCamelCase 情况下,由首字母缩写组成的缩略语和复合词的缩写,应算作单个词。比如,应该使用 Uuid 而非 UUID,使用 Usize 而不是 USize,或者是 Stdin 而不是 StdIn。

  3. 在 snake_case 中,首字母缩写和缩略词是小写的 is_xid_start。

  4. 在 snake_case 或者 SCREAMING_SNAKE_CASE 情况下,每个词不应该由单个字母组成,除非这个字母是最后一个词。比如,使用 btree_map 而不使用 b_tree_map,使用 PI_2 而不使用 PI2 。

  5. 由于历史问题,包名有两种形式 snake_case 或 kebab-case ,但实际在代码中需要引入包名的时候,Rust 只能识别 snake_case,也会自动将 kebab-case 识别为 kebab_case。所以建议使用 snake_case。

  6. 类型转换要遵守 as_,to_,into_ 命名惯例。类型转换应该通过方法调用的方式实现,其中的前缀规则如下:

    方法前缀性能开销所有权改变示例
    as_Freeborrowed -> borrowedstr::as_bytes() 把 str 变成 UTF-8 字节数组,性能开销是 0。输入是一个借用的 &str,输出也是一个借用的 &str
    to_Expensiveborrowed -> borrowed
    borrowed -> owned (non-Copy types)
    owned -> owned (Copy types)
    Path::to_str 会执行一次昂贵的 UTF-8 字节数组检查,输入和输出都是借用的。对于这种情况,如果把方法命名为 as_str 是不正确的,因为这个方法的开销还挺大
    into_Variableowned -> owned (non-Copy types)String::into_bytes() 返回 String 底层的 Vec<u8> 数组,转换本身是零消耗的。该方法获取 String 的所有权,然后返回一个新的有独立所有权的 Vec<u8>
    1. 当一个单独的值被某个类型所包装时,访问该类型的内部值应通过 into_inner() 方法来访问。
    2. 如果 mut 限定符在返回类型中出现,那么在命名上也应该体现出来。例如,Vec::as_mut_slice 就说明它返回了一个 mut 切片
  7. 读访问器(Getter)的名称遵循 Rust 的命名规范

  8. 一个集合上的方法,如果返回迭代器,需遵循命名规则:iter,iter_mut,into_iter

    fn iter(&self) -> Iter             // Iter implements Iterator<Item = &U>
    fn iter_mut(&mut self) -> IterMut  // IterMut implements Iterator<Item = &mut U>
    fn into_iter(self) -> IntoIter     // IntoIter implements Iterator<Item = U>
    
  9. 迭代器的类型应该与产生它的方法名相匹配。例如形如 into_iter() 的方法应该返回一个 IntoIter 类型

代码风格

  制定统一的编码风格可以大大提升代码的可读性,让日常代码维护和团队之间审查代码更加方便。Rust 官方定义了 Rust 代码的整体风格,并且提供了自动化格式化工具 rustfmt 来帮助我们处理代码风格。

  1. 每级缩进必须为 4 个空格,而不是制表符(TAB)进行代码对齐

  2. 一行的最大宽度为 100 个字符,但是完全是注释的行的长度应限制为 80 个字符

  3. 优先使用块缩进而不是视觉缩进

    // Block indent
    a_function_call(
        foo,
        bar,
    );
    
    // Visual indent
    a_function_call(foo,
                    bar);
    
  4. 在任何类型的逗号分隔列表中,在后跟换行符时使用尾随逗号

  5. 用零或一个空行分隔项目和语句。

  6. 函数定义,注意各个关键字的顺序,以及 {} 的位置

    [pub] [unsafe] [extern ["ABI"]] fn foo(arg1: i32, arg2: i32) -> i32 {
        ...
    }
    
  7. 更详细的格式参见 https://doc.rust-lang.org/nightly/style-guide/

注释风格

  在 Rust 中,注释可以分为普通注释和文档注释两类。普通注释使用 ///* ... */ ,文档注释使用 ///(等同于把注释内容写入 #[doc="..."] 里)、//!(等同于把注释内容写入 #![doc=“…”] 里)。注意,注释需要放到要注释的内容的前面。

  1. // 是单行注释,注释内容直到行尾
    在这里插入图片描述

    1. 可以嵌套,但是一般没有这么用的
    2. 不能放到代码语句内部,只能放到代码结尾
    3. 优先选择行注释 ( // ) 而不是块注释 ( /* ... */ )
  2. /* ... */ 为块注释,注释内容在 /**/ 之间可以任意多行
    在这里插入图片描述

    1. 块注释可以嵌套,但是一般没有这么用的
    2. 多行块注释通常在每行前面加一个 * 是常用的风格,但不是必须得
    3. 块注释可以插入到代码语句内部
      在这里插入图片描述
  3. /// 也是单行注释,也有对应的块注释格式为 /** ... */,用于放在函数、结构体、变量等代码前面以对代码进行注释,称为 outer documentation,/// 开头的注释会被 rustdoc 提取用于生成文档,示例如下:
    在这里插入图片描述

    1. 注释的内容主要使用 Markdown 语法来编写
    2. 优先选择行注释 ( /// ) 而不是块注释 ( /** ... */ )
  4. //! 单行注释,也有对应的块注释格式为 /*! ... */,用于放到一个源码文件的开头给一个源码文件注释,称为 inner documentation。//! 开头的注释会被 rustdoc 提取用于生成文档,示例如下:
    在这里插入图片描述

    1. 注释的内容主要使用 Markdown 语法来编写
    2. 当它位于 root crate 文件中时,那么它就是文档主页的注释
    3. 其最后面需要加一个空行
  5. 孤立的 CRs(\r),如果其后没有紧跟有 LF(\n),则不能出现在文档型注释中

参考

  1. https://medium.com/@gftea/rust-language-notes-part-2-7f0b47168029
  2. https://users.rust-lang.org/t/cargo-package-structure-cheat-sheet/47984
  3. https://toml.io/cn/
  4. https://www.andy-pearce.com/blog/posts/2023/May/uncovering-rust-build-and-packaging/
  5. https://cloud.tencent.com/developer/article/2415749
  6. https://course.rs/cargo/reference/workspaces.html
  7. https://rust-coding-guidelines.github.io/rust-coding-guidelines-zh/
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZC·Shou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值