当前位置:网站首页>【Rust笔记】06-包和模块
【Rust笔记】06-包和模块
2022-07-03 08:24:00 【phial03】
06 - 包和模块
6.1 - 包
Cargo.toml
文件可以列取包名,及其指定版本号。用于编译前取得。cargo build
的技巧:--verbose
选项:了解包的协作方式;--crate-type lib
选项:告诉rustc
不要去找main()
函数执行,而是生成一个.rlib
文件,其中包含编译后的代码,可供之后的rustc
命令用作输入。--crate-type bin
选项,编译结果是一个二进制文件。--extern
选项:给出当前包用到的每个库的文件名。--release
选项:产生优化后的代码,运行速度更快,但是编译时间会延长。此时,不会检查整数溢出,且会跳过debug_assert!()
断言。同时,针对诧异生成的栈追踪信息不可靠。
构建分析:
命令行 Cargo.toml 使用的区块 cargo build [profile.dev] cargo build --release [profile.release] cargo test [profile.test] 如果想要分析程序,获得最全面的数据,需要同时启用优化(
--release
选项,用于程序的发布构建)和调试(在开发期间调试构建)符号(symbol),那么必须在cargo.toml
中添加如下代码:[profile.release] debug = true # 在发布构建中启用调试标记
debug
设置控制rustc
中的-g
选项- 此时执行
cargo build --release
可以得到一个带有调试符号的二进制文件。 - 优化设置不受影响。
6.2 - 模块
模块是 Rust 的命名空间,也是函数、类型、常量等构成 Rust 程序或库的容器。
- 创建模块的方式如下所示:
mod mod_name1 { ... } mod mod_name2;
包解决项目间代码共享的问题,模块解决项目内代码组织的问题。
模块是特性项(item)的集合,通过关键字
pub
标记公有或私有特性项,任何没有标记为pub
的特性项都是模块私有的。
6.2.1 - 模块与文件
- 模块可以使用文件创建,在文件中不需要再添加
mod
声明。 - 模块也可以有自己的目录。如果调用一个模块
mod test;
时,既会检查test.rs
文件,也会检查是否存在test/mod.rs
文件。如果两个文件都存在,或者都不存在,会报错。
6.2.2 - 路径和导入
::
操作符:用于访问模块的特性。通过绝对路径来引用。::std
:以双冒号开头,表示引用的是标准库的顶级模块。::std::mem
:引用的是标准库的子模块。::std::mem::swap
:引用的是该模块中的一个公有函数。
use
声明:在整个代码块或者整个模块中,导入一个模块或公有函数。- 支持一次导入多个模块:
use std::collections::{HashMap, HashSet};
。 - 支持导入所有模块:
use std::io::prelude::*
。 - 模块不会自动从自己的父模块继承名字。比如模块
proteins/mod.rs
中有如下声明:
// proteins/mod.rs pub enum AminoAcid { ...} pub mod synthesis;
- 子模块
proteins/synthesis.rs
中的代码,不会自动看到类型AminoAcid
:
// proteins/synthesis.rs pub fn synthesize(seq: &[AminoAcid]) { // 错误:找不到类型AminoAcid ... } // 修改为如下: use super::
- 支持一次导入多个模块:
super
关键字:代表当前模块的父模块。上述代码可修改为如下所示:// proteins/synthesis.rs use super::AminoAcid; pub fn synthesize(seq: &[AminoAcid]) { ... }
self
关键字:代表当前模块。// proteins/mod.rs use self::synthesis::synthesize; use self::AminoAcid::*;
子模块可以访问其父模块中的私有特性项,但必须通过名字导入每一项。使用
super::*;
只会导入那些被标记为pub
的特性项
6.2.3 - 标准前置模块
标准库
std
会自动链接到每个程序中,即包含隐藏的声明extern crate std;
特别常用的名字如:
Vec
和
Result
都会包含在标准前奏里。
std::prelude::vl
是唯一会自动糊导入的前置模块。其中包含了常用的特型和类型。
6.2.4 - 特性项
模块由特性项构成,Rust 的主要特性项如下所示:
函数。
类型。
- 用户自定义类型包括
strust
结构体、enum
枚举和trait
特型。
- 用户自定义类型包括
类型别名:
type
关键字,为现有类型定义一个别名。type Table = HashMap<String, Vec<String>>;
。
impl
块:- 将方法添加到类型上。
- 不能为
impl
标记pub
,只能标记个别的方法。
常量:
const
关键字用于定义常量。与let
的区别,可以标记为pub
,而且必须写明类型。pub const ROOM_TEMPERATURE: f64 = 20.0;
static
关键字定义静态特型,等价于常量。pub static ROOM_TEMPERATURE: f64 = 68.0;
常量的值会编译到代码中使用它的每个地方。
- 常量在代码中,常用于保存魔法数值和字符串。
- 常量没有
mut
。
静态变量是在程序运行前就已经存在,且会持续存在,知道程序退出。
- 静态变量在代码中,常用于保存大量数据,或者用于借用对常量值的引用。
- 静态变量可以标记为
mut
。可修改的静态变量,本质上不是线程安全的,绝对不能在安全代码中使用。
模块:
- 模块可以包含子模块。
- 可以是私有的,也可以是公有的。
导入:
use
关键字。extern crate
关键字。导入一个外部的库。
extern
块:- 声明其他语言编写的函数集合,以便在 Rust 代码中调用它们。
- 如果要在其他包里使用这个块,那么需要将这个函数乃至整个包含模块,都标记为公有。
6.3 - 库
将程序代码改成一个库的步骤:
- 把现有的项目分成两部分:
- 一个要称为库的包,包含所有的共享代码;
- 一个可执行文件,包含仅供现有的命令行程序使用的代码。
- 将这个库发布:
- 将
src/main.rs
重命名为src/lib.rs
。 - 给
src/lib.rs
中,要称为库的公有特型的项添加pub
关键字。 - 把
main
函数临时转移到其他地方。
- 将
- 补充:
- 默认情况下,
cargo build
会从src
代码目录中查找文件,然后决定如何创建。如果看到了src/lib.rs
,那么就知道是要构建一个库。 src/lib.rs
中的代码构成了库的根模块。使用这个库的其他包,只能访问这个根模块中的公有特性。
- 默认情况下,
6.4-src/bin 目录
- cargo 会自动将
src/bin
中的.rs
文件作为要构建的额外程序。
6.5 - 属性
任何特性项都可用属性来修饰。
属性:是写给编译器看的各种指令和建议的普适语法。
常用的属性技巧:
#[allow]
属性:在编译时,禁用某些警告。// 在编译时,不会报告关于non_camel_case_types关键字的警告 #[allow(non_camel_case_types)] pub struct git_revspec { ... }
#[cfg]
属性:将条件编译作为一个特型:// 只在针对安卓编译时包含此模块 #[cfg(target_os = "android")] mod mobile;
常用的
#[cfg]
语法#[cfg(...)]
选项启用场景 test
启用测试(当以 cargo test
或rustc --test
编译时)debug_assertions
启用调试断言(通常用于非优化构建) unix
为 Unix(包括 macOS)编译 windows
为 Windows 编译 target_pointer_width = "64"
针对 64 位平台。另一个可能值是 “32” target_arch = "x86_64"
针对 x86-64 架构,其他的值还有:“x86”、“arm”、“aarch64”、“powerpc”、“powerpc64” 和 “mips” target_os = "macos"
为 macOS 编译。其他的值还有 “windows”、“ios”、“android”、“linux”、“openbasd”、“netbsd”、“dragonfly” 和 “bitrig” feature = "robots"
启用用户定义的名为 “robots” 的特性(当以 cargo build --feature robots
或rustc --cfg feature='"robots"'
编译时)。特性在 Cargo.toml 的[feature]
部分声明not(A)
A 不满足时要提供一个函数的两个不同实现,将其中一个标记为 #[cfg(x)]
,另一个标记为#[cfg(not(x))]
all(A, B)
A 和 B 都满足时,等价于 && any(A, B)
A 或 B 满足时,等价于 ||
#[inline]
属性:对函数的行内扩展,进行一些微观控制。- 如果函数或方法在一个包里定义,但在另一个包里调用,那么 Rust 就不会将其在行内扩展。除非它是泛型的(有类型参数)或者明确标记为
#[inline]
。 #[inline(always)]
,要求每处调用都将函数进行行内扩展。#[inline(never)]
,要求永远不要行内化。
- 如果函数或方法在一个包里定义,但在另一个包里调用,那么 Rust 就不会将其在行内扩展。除非它是泛型的(有类型参数)或者明确标记为
#[cfg]
和#[allow]
,可以添加到整个模块中,并应用于其中所有的特性。#[test]
和#[inline]
,只能添加到个别特性项。要将属性添加给整个包,需要在
main.rs
或lib.rs
文件的顶部、任何特性之前添加,而且要用#!
而不是#
标记。// lib.rs #![allow(non_camel_case_types)] // 可以将属性添加给整个特性项,而不是其后的个别特性项 pub struct git_revspec { ... } pub struct git_error { ... }
#![feature]
属性:用于开启 Rust 语言和库的不安全特性。比如一些新增的测试功能。
6.6 - 测试和文档
#[test]
属性:标记一些函数,表示将要进行单元测试。cargo test
会运行所有#[test]
标记的代码,并生成测试结果。测试中经常使用的两个断言宏,用于检查不变形(invariant):
assert!(expr)
宏:在expr
为true
时成功;否则,会诧异并导致测试失败。assert_eq!(v1, v2)
宏:等价于assert!(v1 == v2)
,但是如果断言失败,那么错误消息会显示两个值。- 即使在发布构建中也会包含
assert!
和assert_eq!
。 - 可以使用
debug_assert!
和debug_assert_eq!
,编写只在调试构建中检查的断言。
标记为
#[test]
的函数会被有条件编译。cargo build
或cargo build --release
会跳过测试代码。当单元测试项比较多,建议放在一个
tests
模块里,并用#[cfg]
属性将整个模块声明为仅供测试使用。#[cfg(test)] // 只在测试构建时包含此模块 mod tests { ... }
Rust 默认通过多个线程运行多个测试。
cargo test testname
可以禁用多线程,那么每次只运行一个测试。
6.6.1 - 集成测试
- 集成测试是放在
tests
目录中的.rs
文件。 tests
目录与src
目录放在一起。- Cargo 会把每个集成测试都编译成一个独立的包,并将其链接到你的库和 Rust 测试套件。
cargo test
既可运行单元测试,也运行集成测试。如果只运行特定的文件(如test/unfurl.rs
)中的集成测试,那么可以执行cargo test --test unfurl
。
6.6.2 - 文档
cargo doc
命令为库创建HTML
文档cargo doc --no-deps --open
--no-deps
选项:使 Cargo 只为当前包生成文档,不考虑它所依赖的包。--open
选项:使 Cargo 生成文档后,就在浏览器中打开。
Cargo 把新生成的文档文件保存在
target/doc
目录中。Cargo 生成的文档基于库中的
pub
特型以及它们对应的文档注释生成。#[doc]
属性:用来标注文档注释。///
开头的注释,会被视作#[doc]
属性;/// test 等价于 #[doc = "test"]
//!
开头的注释,也会被视作#[doc]
属性,可以添加到相应的包含特性,通常是模块或包中。
文档注释中的内容会被按照 Mardown 来解析。
6.6.3 - 文档测试
Rust 会将文档注释中的代码块,自动转换为测试。
/// #
可以隐藏某些代码行。no_run
注解:结合代码块界定符号,可以对特定的代码块禁用测试。/// ```no_run /// ... /// ```
ignore
注解:不希望测试代码被编译。/// ```ignore /// ... /// ```
如果文档注释是其他语言,那么需要使用语言的名字标记。
/// ```c++ /// ... /// ```
6.7 - 指定依赖
指定版本号:
image = "0.6.1"
指定 Git 仓库地址和修订版本:
image = { git = "https://github.com/test/test.git", rev = "rust666" }
指定包含依赖包源代码的目录:
image = { path = "test/image" }
如果发现某个开源包不太合用,那么可以 Fork 下来,然后修改 Cargo.toml 文件中的一行代码即可。接下来,cargo build 会立即切换到 Fork 复制下来的版本,而不再使用官方版本。
6.7.1 - 版本
版本兼容性:
- 以
0.0
开头的版本比较原始,Cargo 不会认为它与任何其他版本兼容。 - 以
0.x
开头的版本,会被认为同其他0.x
的版本兼容。 - 如果项目达到
1.0
版本,只有新的主版本才会破坏兼容性。
- 以
支持使用操作符指定版本:
Cargo.toml 中的写法 含义 image = “=0.10.0” 只使用 0.10.0 版本 image = “>=1.0.5” 使用 1.0.5 版本或更高的版本 image = “>1.0.5 <1.1.9” 使用大于 1.0.5 但小于 1.1.9 的版本 image = “<=2.7.10” 使用小于等于 2.7.10 的版本 另一种指定版本的策略是使用通配符
*
(不常用)。
6.7.2-Cargo.lock
Cargo.lock
可以避免每次构建都升级一次依赖版本。确保在不同的机器上,得到一致的、可再现的构建。- 在第一次构建项目时,Cargo 会输出一个
Cargo.lock
文件,记录项目使用的每个包的确切版本号。后续的构建都会参考这个文件,并继续使用相同的文件。 - 只会在以下操作 Cargo 才会升级:
- 手工修改了
Cargo.toml
文件中的版本号 - 运行了
cargo update
:升级到与Cargo.toml
中指定版本兼容的最新版本。
- 手工修改了
- 如果项目是一个可执行文件,那么应把
Cargo.lock
提交到版本控制系统。任何构建这个项目的人,都可以拿到相同的版本。
6.8-crates.io
cargo package
命令会创建一个文件(target/package/XXX.0.1.0.crate
),其中包含库的所有源文件,以及 Cargo.toml。这个文件可以上传到 crates.io,与世界共享。cargo package --list
可以查看包含什么文件。可以在 Cargo.toml 文件中添加如下许可信息(可删除敏感信息如 authors 中的邮箱):
[package] name = "test_code" version = "0.1.0" authors = ["You <[email protected]>"] license = "MIT" homepage = "https://www.example.com" repository = "https://gitee.com/test/test_code" documentation = "http://www.example.com/docs" description = """ Test Code. """
登录到 crates.io,并取得 API 秘钥(需要保密),然后只在可控制的安全计算机上执行:
cargo login ^^&@*&...
最后执行
cargo publish
来发布库到 crates.io 上。
6.9 - 工作空间
Cargo 会保证每个包都有自己的构建目录:
target
,包含一份这个包所依赖的独立构建。Cargo 工作空间(workspace):共享相同构建目录和
Cargo.lock
文件的一组包。可以节省编译时间和磁盘空间。创建工作空间的方法:
在存储库的根目录下创建一个
Cargo.toml
文件在其中添加如下代码:其中
fern_sim
等是包含各自包的子目录的名称。[workspace] members = ["fern_sim", "fern_img", "fern_video"]
把各个子目录中的
Cargo.lock
文件和taerget
目录都删除。做完这些后,无论在任何包中运行
cargo build
,都会自动在根目录中创建一个共享的构建目录,所有包都共享。
cargo build --all
命令会构建当前工作空间中所有的包。cargo test
和cargo doc
也支持--all
选项。
6.10 - 其他内容
- 当在
crates.io
上发布了开源包后,包的文档会自动发布到docs.rs
。 - 如果项目在 Github 上,Travis CI 可以在每次推送代码时,自动构建和测试。详情查看(https://travis-ci.org/)。
- 可以基于包的顶级文档注释生成一个
README.md
文件。由第三方插件提供cargo install readme
。支持cargo readme --help
来学习这个插件。
详见《Rust 程序设计》(吉姆 - 布兰迪、贾森 - 奥伦多夫著,李松峰译)第八章
原文地址
边栏推荐
- Map的实现类的顺序性
- 【K&R】中文第二版 个人题解 Chapter1
- UE4 source code reading_ Bone model and animation system_ Animation process
- Flex flexible box layout
- Conversion between golang JSON format and structure
- Compilation error: "not in executable format: file format not recognized"“
- Unity editor expansion - the framework and context of unity imgui
- LinkList
- LinkedList set
- Osgearth north arrow display
猜你喜欢
OpenGL learning notes
图像处理8-CNN图像分类
數據庫應用技術課程設計之商城管理系統
C course design employee information management system
UE4 source code reading_ Bone model and animation system_ Animation compression
Base64和Base64URL
Gradle's method of dynamically modifying APK package name
P1596 [USACO10OCT]Lake Counting S
Dealing with duplicate data in Excel with xlwings
Thymeleaf 404 reports an error: there was unexpected error (type=not found, status=404)
随机推荐
php-fpm软件的安装+openresty高速缓存搭建
Golang中删除字符串的最后一个字符
Minimap plug-in
jupyter远程服务器配置以及服务器开机自启
Basic operation and process control
十六进制编码简介
Osgearth target selection
Golang url的编码和解码
LinkList
Shader foundation 01
UE4 source code reading_ Bone model and animation system_ Animation process
数据库应用技术课程设计之商城管理系统
[cloud native] introduction and use of feign of microservices
How does unity fixedupdate call at a fixed frame rate
[set theory] order relation (the relation between elements of partial order set | comparable | strictly less than | covering | Haas diagram)
CLion-Toolchains are not configured Configure Disable profile问题解决
Chocolate installation
[public key cryptography] ECC elliptic cryptosystem (implementing ElGamal encryption method)
Sequence of map implementation classes
Base64和Base64URL