文中提到的书籍: 《Rust 程序设计语言》BK (中文), 《通过例子学 Rust》EX (中文), 《标准库文档》STD, 《Rust 秘典》NOM (中文), 《Rust 参考手册》REF (中文).
可点击凡例
BK 《Rust 程序设计语言》
EX 《通过例子学 Rust》
STD 《标准库文档》
NOM 《Rust 秘典》(死灵书)
REF 《Rust 参考手册》
RFC 官方 RFC 文档
🔗 外部链接
↑ 参见上文
↓ 参见下文其他凡例
🗑️ 基本已废弃
'18 有最低版本要求.
🚧 要求Rust nightly (或不完整).
🛑 故意的错误示例或缺陷代码.
🝖 略显深奥, 很少使用的高级我.
🔥 常用特性
? 缺少链接或说明
💬 作者见解
..=, =>
)
展开所有内容?
夜间模式 💡
语言结构
增强设施
数据布局
附录
标准库
工具
与类型打交道
编码指南
如果你是 Rust 新手, 或者你想试点什么, 可以在下面尝试运行一下:
你可能会遇到的问题
unsafe
的第三方库有可能会破坏安全性保证.1 参见该 Rust 调查结果.
下载
IDE 编辑器
模块化初学者资源
作者按 💬 — 如果你从来没用过 Rust 最好还是先看看上面的教程, 本文后续章节对相关概念仅作简要说明, 不会深入讲解.
数据类型和内存位置由关键字定义.
示例 | 说明 |
---|---|
struct S {} | 定义包含命名字段的结构体BK EX STD REF. |
struct S { x: T } | 定义包含 T 类型命名字段 x 的结构体. |
struct S (T); | 定义 T 类型数字字段 .0 的“元组”结构体. |
struct S; | 定义一个零大小NOM的单元结构体. 已优化不占用任何空间. |
enum E {} | 定义枚举BK EX REF, 参见数字数据类型和标签联合. |
enum E { A, B (), C {} } | 定义变体枚举; 可以是单元- A , 元组 B () 或者结构体风格的 C{} . |
enum E { A = 1 } | 如果所有变体都是单元值, 则允许判别式值, 例如用于 FFI. |
union U {} | 不安全的 C 风格联合体REF, 用于兼容 FFI. 🝖 |
static X: T = T(); | 具有 'static 生命周期的全局变量 BK
EX REF, 内存位置独立. |
const X: T = T(); | 定义常量BK EX REF, 使用时会临时复制一份. |
let x: T; | 在栈1上分配 T 大小的字节并命名为 x . 一旦分配不可修改. |
let mut x: T; | 类似于 let , 但具有可变性BK
EX, 允许修改和可变借用2. |
x = y; | 将 y 移动到 x , 如果 T 不能 Copy STD, y 将不再可用, 否则会复制一份 y . |
1 域变量 BK
EX REF是生存在栈上的同步代码. 在async{}
中, 这些变量将成为异步状态机的一部分, 最终可能是驻留在堆上.
2 注意术语_可变_和_不可变_并不准确. 不可变绑定或共享引用可能仍然包含 Cell STD, 从而提供_内部可变性_.
下面列出了如何构建和访问数据结构; 以及一些_神奇的_类型.
示例 | 说明 |
---|---|
S { x: y } | 构建 struct S {} 或者 use 的 enum E::S {} 字段 x 设置为 y . |
S { x } | 同上, 但字段 x 会被设置为局部变量 x . |
S { ..s } | 用 s 填充剩余字段, 常配合 Default 一起使用. |
S { 0: x } | 类似下面的 S (x) , 但是用结构体语法设置字段 .0 . |
S (x) | 创建 struct S (T) 或者 use 的 enum E::S () 其中字段 .0 设置为 x . |
S | 表示 struct S; 或以 S 为值创建 use 来的 enum E::S . |
E::C { x: y } | 构建枚举变体 C . 上面的其他方法依然有效. |
() | 空元组, 既是字面量也是类型, 又称单元. STD |
(x) | 括号表达式. |
(x,) | 单元素元组表达式. EX STD REF |
(S,) | 单元素元组类型. |
[S] | 未指明长度的数组类型, 如切片. EX STD REF 不能生存在栈上. * |
[S; n] | 元素类型为 S , 定长为 n 的数组类型 EX STD. |
[x; n] | 由 n 个 x 的副本构建的数组实例. REF |
[x, y] | 由给定元素 x 和 y 构成的数据实例. |
x[0] | 索引, 下标为 usize 类型. 可重载 Index 和 IndexMut. |
x[..] | 范围索引, 这里是_全部范围_, 又见下 x[a..b] , x[a..=b] , ... |
a..b | 左闭右开区间 STD REF, 如 1..3 表示 1, 2 . |
..b | 无起点到右开区间 STD. |
a..=b | 全闭合区间, STD 1..=3 表示 1, 2, 3 . |
..=b | 无起点到右闭合区间 STD. |
.. | 全包含区间STD, 常表示_整个组合_. |
s.x | 命名 字段访问REF, 如果 x 不是类型 S 的一部分的话则会尝试 Deref. |
s.0 | 数字字段访问, 用于元组类型 S (T) . |
为非所有者内存赋予访问权限. 另请参见 泛型 & 约束.
示例 | 说明 |
---|---|
&S | 共享 引用 BK
STD NOM REF (用于存储_任意_&s ). |
&[S] | 特殊的切片引用, 包含地址和长度 (address , length ). |
&str | 特殊的字符串引用, 包含地址和长度 (address , length ). |
&mut S | 允许修改的独占引用 (参见 &mut [S] , &mut dyn S , …). |
&dyn T | 特殊的 trait 对象 BK
引用, 包含地址和虚表 (address , vtable ). |
&s | 共享借用 BK
EX STD (该 s 的地址, 长度, 虚表等, 如 0x1234 ). |
&mut s | 具有可变性的独占借用. EX |
*const S | 不可变的裸指针类型 BK STD REF, 内存不安全 |
*mut S | 可变的裸指针类型, 内存不安全. |
&raw const s | 不通过引用创建裸指针, 见 ptr:addr_of!() STD 🚧 🝖 |
&raw mut s | 同上, 但可变. 🚧 裸指针可用于未对齐的包装字段. 🝖 |
ref s | 引用绑定, EX 创建绑定的引用类型. 🗑️ |
let ref r = s; | 等价于 let r = &s . |
let S { ref mut x } = s; | 可变引用绑定 (let x = &mut s.x ), 解构↓的简写. |
*r | 解引用 BK
STD NOM 引用 r 以访问指针指向的内容. |
*r = s; | 如果 r 是一个可变引用, 则将 s 移动或复制到目标内存. |
s = *r; | 如果 r 可 Copy , 则将 r 引用的内容复制到 s . |
s = *my_box; | Box 有一个特例🔗, 即便它不可 Copy , 也仍会从 Box 里面移动出来. |
'a | 生命周期参数, BK EX NOM REF, 为静态分析声明一块代码的持续时间. |
&'a S | 仅支持生存时间不短于 'a 的地址 s . |
&'a mut S | 同上, 但允许改变地址指向的内容. |
struct S<'a> {} | 表明 S 包含一个生命周期为 'a 的地址.由 S 的创建者决定 'a . |
trait T<'a> {} | 表明一个实现了 impl T for S 的 S 可能会包含地址. |
fn f<'a>(t: &'a T) | 同上, 用于函数.调用者决定 'a . |
'static | 特殊的生命周期, 生存在程序的整个执行过程中. |
定义代码单元及其抽象.
示例 | 说明 |
---|---|
trait T {} | 定义 trait BK EX REF, 它是一系列可被实现的通用行为. |
trait T : R {} | T 是父 trait REF R 的子 trait.任何要 impl T 的 S 都必须先 impl R . |
impl S {} | 类型 S 的函数实现 REF, 如方法. |
impl T for S {} | 为类型 S 实现 trait T . |
impl !T for S {} | 禁用自动推导的 auto trait. NOM REF 🚧 🝖 |
fn f() {} | 定义一个函数BK
EX REF, 或在 impl 里关联一个函数. |
fn f() -> S {} | 同上, 但会返回一个 S 类型的值. |
fn f(&self) {} | 定义一个方法.BK
EX例如, 在 impl S {} 中. |
const fn f() {} | 编译期常量函数 fn .如 const X: u32 = f(Y) . '18 |
async fn f() {} | 异步 REF '18 函数转写↓. 令 f 返回一个 impl Future . STD |
async fn f() -> S {} | 同上, 但令 f 返回 impl Future<Output=S> . |
async { x } | 用在函数内部, 使 { x } 变得 impl Future<Output=X> . |
fn() -> S | 函数指针BK STD REF, 内存存放的可调用地址. |
Fn() -> S | 可调用 TraitBK
STD(又见 FnMut 和 FnOnce ), 可由闭包或函数等实现. |
|| {} | 闭包 BK EX REF , 将会借用它所有的捕获.↓ REF (如局部变量). |
|x| {} | 有传入参数 x 的闭包. |
|x| x + x | 没有块表达式的闭包, 仅可由单个表达式组成. |
move |x| x + y | 闭包, 将会获取它所有捕获的所有权. |
return || true | 闭包, 看起来像是逻辑或, 但这里表示返回一个闭包. |
unsafe | 不安全代码.↓ BK EX NOM REF . 如果你喜欢在周五晚上调试段错误的话~ |
unsafe fn f() {} | 意味着“调用会导致 UB↓, 必须检查依赖.” |
unsafe trait T {} | 意味着“不完善的 impl T 会导致 UB, 必需检查实现.” |
unsafe { f(); } | 向编译器保证“我已检查过依赖, 请相信我.” |
unsafe impl T for S {} | 保证“S 的行为确实符合 T ”, 在 S 上使用 T 是安全的. |
在函数中控制执行.
示例 | 说明 |
---|---|
while x {} | 循环REF, 当表达式 x 为真时运行. |
loop {} | 无限循环REF直到 break . 可以用 break x 来 yield 一个值出来. |
for x in iter {} | 在迭代器上循环的语法糖.BK STD REF |
if x {} else {} | 条件分支 REF. 如果表达式为真则...否则... |
'label: loop {} | 循环标签 EX REF, 用于嵌套循环的流程控制. |
break | Break 表达式 REF, 用于退出循环. |
break x | 同上, 但将 x 作为循环表达式的值(仅在 loop 中有效). |
break 'label | 不单单退出的是当前循环, 而是最近一个标记有 'label 的循环. |
break 'label x | 同上, 但返回 x 作为闭包循环 'label 的值. |
continue | Continue 表达式 REF, 用于继续该循环的下一次迭代. |
continue 'label | 同上, 但继续的是最近标记有 'label 的循环迭代. |
x? | 如果 x 是 Err 或 None, 返回并向上传播.BK
EX STD REF |
x.await | 仅在 async 中可用. yield 当前控制流直到 Future STD 或流 x 已就绪. REF '18 |
return x | 从函数中提前返回.然而以表达式结束的方式更惯用. |
f() | 调用 f (如函数, 闭包, 函数指针或 Fn 等). |
x.f() | 调用成员函数(方法), 要求 f 以 self , &self 等作为第一个参数. |
X::f(x) | 同 x.f() .除非 impl Copy for X {} , 否则 f 仅可调用一次. |
X::f(&x) | 同 x.f() . |
X::f(&mut x) | 同 x.f() . |
S::f(&x) | 同 x.f() , 仅当 X 实现了对 S 的 Deref.这里 x.f() 会去找 S 的方法. |
T::f(&x) | 同 x.f() , 仅当 X impl T . 这里 x.f() 会去找作用域内 T 的方法. |
X::f() | 调用关联函数, 比如 X::new() . |
<X as T>::f() | 调用为 X 实现了的 trait 方法 `T::f(). |
将项目分割成更小的单元并最大限度地减少依赖关系.
示例 | 说明 |
---|---|
mod m {} | 定义模块BK
EX REF, 其中的定义在 {} 内. ↓ |
mod m; | 定义模块, 其中的定义在 m.rs 或 m/mod.rs . ↓ |
a::b | 命名空间路径EX REF, 表示 a (mod 或 enum 等) 里面的元素 b . |
::b | 相对于当前 crate 根下搜索 b .🗑️ |
crate::b | 相对于当前 crate 根下搜索 b .'18 |
self::b | 相对于当前模块下搜索 b . |
super::b | 相对于父级模块下搜索 b . |
use a::b; | Use EX REF 声明, 将 b 直接引入到当前作用域, 以后就不需要再加 a 前缀了. |
use a::{b, c}; | 同上, 但同时将 b 和 c 都引入. |
use a::b as x; | 将 b 引入作用域但命名为 x . 比如 use std::error::Error as E . |
use a::b as _; | 将 b 匿名的引入作用域, 用于含有冲突名称的 trait. |
use a::*; | 将 a 里面的所有元素都引入作用域.仅推荐在 a 为 prelude 的情况下使用.🔗 |
pub use a::b; | 将 a::b 引入作用域, 并再次从当前位置导出. |
pub T | 控制 T 的可见性 BK
.「如果父级路径公开, 我也公开」. |
pub(crate) T | 可见性仅1在当前 crate 内. |
pub(super) T | 可见性仅1在父级以下. |
pub(self) T | 可见性仅1在当前模块内. |
pub(in a::b) T | 可见性仅1在 a::b 内. |
extern crate a; | 声明依赖一个外部 crate BK
EX REF 🗑️. 换用 use a::b '18. |
extern "C" {} | 声明 FFI 的外部依赖和 ABI(如 "C" )BK
EX NOM REF. |
extern "C" fn f() {} | 定义 FFI 导出成 ABI(如 "C" )的函数. |
1 子模块中的总是可以访问所有项目, 与其是否是 pub
无关.
类型名称的简写, 以及转为其他类型的方法.
示例 | 说明 |
---|---|
type T = S; | 创建类型别名BK
REF. 这里表示 S 的另一个名字. |
Self | 当前实现类型REF 的别名. 如 fn new() -> Self . |
self | fn f(self) {} 的方法主体. 同 fn f(self: Self) {} . |
&self | 同上, 但将借用指向自己的引用. 同 f(self: &Self) . |
&mut self | 同上, 但是可变借用. 同 f(self: &mut Self) . |
self: Box<Self> | 任意自型, 为智能指针增加方法 (my_box.f_of_self() ). |
S as T | 消歧义BK
REF, 将类型 S 作为 trait T 看待. 比如 <X as T>::f() . |
S as R | 在 use 里, 将 S 导入为 R .如 use a::b as x . |
x as u32 | 裸转换EX REF, 会发生截断和一些比特上的意外.1 NOM |
1 关于在类型之间转换的所有方法, 请参见下面的类型转换.
实际编译前的代码预展开.
示例 | 说明 |
---|---|
m!() | 宏 BK
STD REF 咒语, 也作 m!{} , m![] (取决于宏本身) |
#[attr] | 外部属性EX REF, 注解接下来的内容. |
#![attr] | 内部属性, 注解_上部_, 周边的内容. |
宏内写法 | 说明 |
---|---|
$x:ty | 宏捕获 (此处表示捕获一个类型), 详见工具链命令↓. |
$x | 宏替换, 如使用上面的 $x:ty 捕获. |
$(x),* | 宏重复“零次或若干次”. |
$(x),? | 宏重复“零次或一次”. |
$(x),+ | 宏重复“一次或若干次”. |
$(x)<<+ | 分隔符可以不是逗号“, ”. 比如这里用 << 作为分割符. |
函数参数, match
或 let
表达式中的构造.
示例 | 说明 |
---|---|
match m {} | 模式匹配BK EX REF, 下面跟匹配分支. 参见下表. |
let S(x) = get(); | 显然, let 也和下表的模式匹配类似. |
let S { x } = s; | 仅将 x 绑定到值 s.x . |
let (_, b, _) = abc; | 仅将 b 绑定到值 abc.1 . |
let (a, ..) = abc; | 也可以将「剩余的」都忽略掉. |
let (.., a, b) = (1, 2); | 忽略前面「剩余的」, 这里 a 是 1 , b 是 2 . |
let s @ S { x } = get(); | 将 s 绑定到 S 并将 x 绑定到 s.x , 模式绑定, BK
EX REF 见下 🝖 |
let w @ t @ f = get(); | 存储 3 份 get() 结果的拷贝分别到 w , t , f . 🝖 |
let Some(x) = get(); | 不可用🛑, 因为模式可能会不匹配REF. 换用 if let . |
if let Some(x) = get() {} | 如果模式匹配则执行该分支(如某个 enum 变体). 语法糖*. |
while let Some(x) = get() {} | 等效; 这里继续调用 get() , 只要可以分配模式就运行 {} . |
fn f(S { x }: S) | 类似于 let , 模式匹配也可用在函数参数上. 这里 f(s) 的 x 被绑定到 s.x .🝖 |
* 展开后是 match get() { Some(x) => {}, _ => () }
.
match
表达式的模式匹配分支. 左列的分支也可用于 let
表达式.
匹配分支 | 说明 |
---|---|
E::A => {} | 匹配枚举变体 A . 参见模式匹配.BK
EX REF |
E::B ( .. ) => {} | 匹配枚举元组变体 B , 通配所有下标. |
E::C { .. } => {} | 匹配枚举结构变体 C , 通配所有字段. |
S { x: 0, y: 1 } => {} | 匹配含特定值的结构体(仅匹配 s 的 s.x 为 0 且 s.y 为 1 的情况). |
S { x: a, y: b } => {} | 匹配为任意(!)值的该类型结构体, 并绑定 s.x 到 a , 绑定 s.y 到 b . |
S { x, y } => {} | 同上, 但将 s.x 和 s.y 分别简写地绑定为 x 和 y . |
S { .. } => {} | 匹配任意值的该类型结构体. |
D => {} | 匹配枚举变体 E::D .仅当 D 已由 use 引入. |
D => {} | 匹配任意事物并绑定到 D .如果 D 没被 use 进来, 怕不是个 E::D 的假朋友.🛑 |
_ => {} | 通配所有, 或者所有剩下的. |
0 | 1 => {} | 可选模式列表, 或模式. RFC |
E::A | E::Z | 同上, 但为枚举变体. |
E::C {x} | E::D {x} | 同上, 但如果所有变体都有 x 则绑定. |
Some(A | B) | 同上, 可以嵌套匹配. |
(a, 0) => {} | 匹配元组, 绑定第一个值到 a , 要求第二个是 0 . |
[a, 0] => {} | 切片模式REF 🔗. 绑定第一个值到 a , 要求第二个是 0 . |
[1, ..] => {} | 匹配以 1 开始的数组, 剩下的不管. 子切片模式.? |
[1, .., 5] => {} | 匹配以 1 开始以 5 结束的数组. |
[1, x @ .., 5] => {} | 同上, 但将 x 绑定到中间部分的切片上(见匹配绑定) |
[a, x @ .., b] => {} | 同上, 但可以指定任意上下界 a , b . |
1 .. 3 => {} | 范围模式, BK
REF 这里匹配 1 和 2 . 尚不稳定. 🚧 |
1 ..= 3 => {} | 闭区间范围模式, 匹配 1 , 2 和 3 . |
1 .. => {} | 开区间范围模式, 匹配 1 和更大的数字. |
x @ 1..=5 => {} | 绑定匹配到 x , 即模式绑定BK
EX REF. 这里 x 可以是 1 , 2 直到 5 . |
Err(x @ Error {..}) => {} | 嵌套使用, 这里 x 绑定到 Error , 下常跟 if . |
S { x } if x > 10 => {} | 模式匹配条件BK EX REF. 该匹配会要求这个条件也为真. |
泛型使得类型构造, trait 和函数更加可扩展.
示例 | 说明 |
---|---|
S<T> | 泛型BK
EX, 类型参数 T 是占位符. |
S<T: R> | 类型短 trait 约束BK
EX说明. (R 必须 是个实际的 trait). |
T: R, P: S | 独立 trait 约束(这里一个对 T , 一个对 P ). |
T: R, S | 编译错误🛑. 可以用下面的 R + S 代替. |
T: R + S | 合并 trait 约束BK
EX. T 必须同时满足 R 和 S . |
T: R + 'a | 同上, 但有生命周期. T 必须满足 R ; 如果 T 有生命周期, 则必须长于 'a . |
T: ?Sized | 指定一个预定义的 trait 绑定, 如这里是 Sized . ? |
T: 'a | 类型生命周期约束EX. T 应长于 'a . |
T: 'static | 同上. 但 不 意味着值 t 会 🛑 活在 'static 上, 仅在它可以的时候才行. |
'b: 'a | 生命周期 'b 必须至少活得和 'a 一样长. |
S<const N: usize> | 泛型常量绑定. ? 使用类型 S 可提供常量值 N . |
S<10> | 可以指定字面量. |
S<{5+5}> | 表达式需要用花括号包起来. |
S<T> where T: R | 几乎等同于 S<T: R> , 但对于比较长的约束更容易阅读. |
S<T> where u8: R<T> | 还允许您创建涉及其他类型的条件语句. |
S<T = R> | 默认参数. BK 保持扩展性的同时更易于使用. |
S<const N: u8 = 0> | 常量默认参数. 如 f(x: S) {} 中参数 N 为 0 . |
S<T = u8> | 类型默认参数. 如 f(x: S) {} 中参数 T 为 u8 . |
S<'_> | 推断匿名生命周期. 让编译器 “想办法” 明确生命周期. |
S<_> | 推断匿名类型. 比如 let x: Vec<_> = iter.collect() |
S::<T> | TurbofishSTD 消歧义类型调用. 如 f::<u32>() |
trait T<X> {} | X 的 trait 泛型.可以有多个 impl T for S (每个 X 一个). |
trait T { type X; } | 定义关联类型BK
REF X . 仅可有一个 impl T for S . |
type X = R; | 设置关联类型. 仅在 impl T for S { type X = R; } 内. |
impl<T> S<T> {} | 实现 S<T> 任意类型 T 的功能. |
impl S<T> {} | 实现确定 S<T> 的功能. 如 S<u32> . |
fn f() -> impl T | Existential 类型 BK
. 返回一个对调用者未知的但 impl T 的 S . |
fn f(x: &impl T) | Trait 约束, 「impl trait」BK
.和 fn f<S:T>(x: &S) 有点类似. |
fn f(x: &dyn T) | 动态分发标记BK
REF. f 不再单态. |
fn f() where Self: R | 在 trait T {} 中标记 f 仅可由实现了 impl R 的类型访问. |
fn f() where Self: Sized; | 使用 Sized 可以指定 f 对于 dyn T 的 trait 对象对应的虚表. |
fn f() where Self: R {} | Other R useful w. dflt. methods (non dflt. would need be impl'ed anyway). |
实际的 类型和 trait, 某些事物的抽象以及常用生命周期.
示例 | 说明 |
---|---|
for<'a> | 高阶绑定NOM REF表记. 🝖 |
trait T: for<'a> R<'a> {} | 在任意生命周期下, 任意实现了 impl T 的 S 都应满足 R . |
fn(&'a u8) | 函数指针 类型, 持有可调用 fn 以及指定的生命周期 'a . |
for<'a> fn(&'a u8) | 高阶类型1 🔗 持有可调用 fn 任意 小于 上述生命周期的参数; 上面的子类型. |
fn(&'_ u8) | 同上, 自动展开为类型 for<'a> fn(&'a u8) . |
fn(&u8) | 同上, 自动展开为类型 for<'a> fn(&'a u8) . |
dyn for<'a> Fn(&'a u8) | 高阶 (trait 对象) 类型, 行为如上 fn . |
dyn Fn(&'_ u8) | 同上, 自动展开为类型 dyn for<'a> Fn(&'a u8) . |
dyn Fn(&u8) | 同上, 自动展开为类型 dyn for<'a> Fn(&'a u8) . |
1 没错, for<>
是类型的一部分, 这会导致你下面会写出来 impl T for for<'a> fn(&'a u8)
这样的代码.
Trait 实现 | 说明 |
---|---|
impl<'a> T for fn(&'a u8) {} | For fn 指针, 调用接受指定 小于 'a 的参数, impl trait T . |
impl T for for<'a> fn(&'a u8) {} | For fn 指针, 调用接受任意 小于 的参数, impl trait T . |
impl T for fn(&u8) {} | 同上, 简写. |
Rust 提供了若干种创建字符串和字符字面量的办法.
示例 | 说明 |
---|---|
"..." | UTF-8 字符串字面量REF, 1.会将 \n 等看作换行 0xA 等. |
r"..." | UTF-8 裸字符串字面量REF, 1. 不会处理 \n 等. |
r#"..."# 等 | UTF-8 裸字符串字面量. 但可以包含 " . |
b"..." | 字节串字面量REF, 1, 由 ASCII [u8] 组成. 并不是字 符 串. |
br"..." , br#"..."# 等 | 裸字节串字面量, ASCII [u8] . 说明见上. |
'🦀' | 字符字面量REF, 固定的 4 字节 Unicode '字符'.STD |
b'x' | ASCII 字节字面量.REF |
1 均支持多行字符串. 但要注意 Debug
↓ (例如 dbg!(x)
和 println!("{x:?}")
) 会将换行符渲染成 \n
, 而 Display
↓ (例如 println!("{x}")
) 则会输出换行.
调试器的天敌. 这玩意儿能避免 Bug.
示例 | 说明 |
---|---|
/// | 外部行级文档注释, BK EX REF 用于类型, trait, 函数等. |
//! | 内部行级文档注释, 多用于文档模块的文件头部. |
// | 行内注释. 用于文档代码流内或 内部组件. |
/*...*/ | 块级注释. |
/**...*/ | 外部块级文档注释. |
/*!...*/ | 内部块级文档注释. |
工具链命令↓告诉你可以在文档注释中做什么.
这些小技巧不属于其他分类但最好了解一下.
示例 | 说明 |
---|---|
! | 永远为空的 never 类型.🚧 BK EX STD REF |
_ | 无名变量绑定.如 |x, _| {} . |
let _ = x; | 匿名赋值等于无操作 (no-op), 不会🛑将 x 移出当前作用域! |
_x | 变量绑定, 明确标记该变量未使用. |
1_234_567 | 为了易读加入的数字分隔符. |
1_u8 | 数字字面量的类型说明符.EX REF (又见 i8 , u16 等). |
0xBEEF , 0o777 , 0b1001 | 十六进制(0x ), 八进制(0o )和二进制(0b ) 整型字面量. |
r#foo | 原始标识符 BK EX. 用于版本兼容. 🝖 |
x; | 语句REF终止符. 见表达式EX REF. |
Rust 支持大部分其他语言也有的通用操作符(+
, *
, %
, =
, ==
...). 因为这在 Rust 里没什么太大差别所以这里不列出来了. Rust 也支持运算符重载.STD
可能会导致你的脑子爆炸的神秘知识点, 超级推荐.
同 C
/C++
, Rust 基于一个 抽象层.
→
→
→
抽象层 (AM)
如果 Rust 直接发送给 CPU, 人们可能会错误地认为他们 应该逃脱惩罚 , 然而 更加正确 的做法是:
无抽象层 | 有抽象层 |
---|---|
0xffff_ffff 会产生一个有效的 char . 🛑 | 只是内存中的一段比特. |
0xff 和 0xff 是相同的指针. 🛑 | 指针可以来自不同的 域. |
在 0xff 的任意读写都是可行的. 🛑 | 不能同时读写同一引用. |
某些寄存器直接把 0x0 当做 null. 🛑 | 在引用中保存 0x0 简直是克苏鲁. |
如果有什么东西让你觉得, “不该能用的啊”, 那可能就是这里的原因.
名称 | 说明 |
---|---|
强转 NOM | 隐式转换 类型以匹配签名, 如 &mut T 转为 &T . 见 类型转换. ↓ |
解引用 NOM 🔗 | 连续解引用 x: T 直到 *x , **x , … 满足目标类型 S . |
Prelude STD | 自动导入基本项目, 如 Option , drop , ... |
重新借用 | 即便 x: &mut T 不能复制, 也可以移动一个新的 &mut *x 代替. |
生命周期省略 BK NOM REF | 自动将 f(x: &T) 标注为 f<'a>(x: &'a T) . |
方法重解析 REF | 解引用或借用 x 直到 x.f() 可用. |
匹配引用简写 RFC | 重复应用解引用到各个选择肢上并添加 ref 和 ref mut 到绑定. |
右值静态提升 RFC | 使引用满足 'static , 如 &42 , &None , &mut [] . |
作者按 💬 — 上述功能会让你活得轻松些, 但却会扰乱你的理解. 如果任意有类型相关的操作让你觉得 有些反常, 那可能就是这里的语法糖在作怪了.
移动, 引用和生命周期到底是咋回事.
Box<T>
的栈代理来指向),&str
的字符串 str
),1 对于固定大小的值, 栈的管理非常细致: 你需要的时候马上生成, 不需要的时候马上离开. 然而这些 短暂 分配的指针却是导致生命周期存在的 本质 原因, 也是主导了本章后续所有内容.
let t = S(1);
t
, 类型为 S
, 里面存储的值为 S(1)
.let
那空间将会分配在栈上. 10x7
(“告诉我某变量的地址”),S(1)
(“增加某变量”).t
指的是 t
的位置 (这里是 0x7
) 和 t
里面的 值 (这里是 S(1)
).1 上述↑比较中仅针对于同步代码, 而 async
异步栈帧有可能被运行时放在堆上.
let a = t;
t
里面的值到 a
的位置, 如果 S
是可 Copy
的则复制一份.t
的位置移动后将会失效且不能再被读取.
unsafe
访问 t
的话它仍有可能 看起来 像是个有效的 S
,
但任何把它当成有效 S
的操作都是未定义行为 (UB). ↓Copy
的影响, 虽然它会轻微影响上述规则:
let c: S = M::new();
M::new()
的字节无法被有效地转换为 S
类型.as
转换等).{
let mut c = S(2);
c = S(3); // <- 赋值前将会对 `c` 进行析构.
let t = S(1);
let a = t;
} // <- 这里退出了 `a`, `t`, `c` 的作用域, 将调用 `a`, `c` 的析构方法.
{}
块时执行,Drop::drop()
时.
drop()
在 a
上调用了一次, 在 c
上调用了两次, 但没有在 t
上调用过.Copy
的值都在此时发生析构, 除非使用了 mem::forget()
, Rc
之类或者 abort()
.fn f(x: S) { ... }
let a = S(1); // <- 在这里
f(a);
f
之前, a
中的值将会移动到“商量”好的栈位置, 当 f
执行时作为“局部变量” x
.1 实际的位置取决于调用时的转换, 可能根本不会分配在栈上, 但这不影响此处的心智模型.
fn f(x: S) {
if once() { f(x) } // <- 递归前在这里
}
let a = S(1);
f(a);
fn f(x: S) {
if once() { f(x) }
let m = M::new() // <- 递归后在这里
}
let a = S(1);
f(a);
f
的递归产生的第二个 x
, 将会在后续的递归里由 m
重新利用.关键点在于, 有很多种方法来保证之前保有一个确定类型有效值内存位置在此期间不被使用. 简单来说就是实现了指针.
let a = S(1);
let r: &S = &a;
&S
或 &mut S
这样的引用类型持有某个 s
的位置.&S
绑定到了名称 r
, 持有变量 a
(0x3
) 的 位置, 它的类型为 S
, 通过 &a
获取.c
视为 指定位置, 引用 r
就是 位置的接入点.let r: &S = &a;
let r = &a;
let mut a = S(1);
let r = &mut a;
let d = r.clone(); // 从 r 指向目标克隆或拷贝.
*r = S(2); // 将 r 指向目标设置为新值 S.
&S
) 或写入到 (&mut S
) 它们指向的位置.*r
表示既不使用 r
的 位置 也不使用其包含的 值, 而是使用位置 r
指向的目标.d
是由 *r
创建的, 并且 S(2)
写入到了 *r
.
Clone::clone(&T)
期望传入其自身的引用, 这就是我们使用 r
而非 *r
的原因.*r = ...
中的旧值也会被析构 (图中未说明).let mut a = ...;
let r = &mut a;
let d = *r; // 无法移出值, 否则 `a` 将为空.
*r = M::new(); // 无法存储非 S 类型值, 毫无意义.
&mut T
必须和变量一样提供保证, 要知道它们并不能让指向的目标消失:
let p: *const S = questionable_origin();
unsafe
的, 将无效的 *p
作为有效值来操作是未定义行为 (UB). ↓r: &'a S
r
本身作为代码行的“存在时间”并无关联 (它只要存在得更短就行了).&'static S
意味着地址必须 在所有代码行中有效.1 文档中的 作用域 和 生命周期 有时存在歧义. 这里尝试作一定的区分, 但如果有更好的意见也欢迎提出.
2 生存行 可能是个更好的说法...
r: &'c S
的含义r: &'c S
, 它表示:
r
持有某个 S
的地址,r
指向的任意地址会至少存在在 'c
期间,r
本身不能活得比 'c
长.{
let b = S(3);
{
let c = S(2);
let r: &'c S = &c; // 不能如愿运行
{ // 因为函数体中的局部变量无法命名生命周期
let a = S(0); // 函数中的规则也类似
r = &a; // `a` 的位置在很多行都不存在 -> 不行.
r = &b; // `b` 的位置活在比 `c` 更大的范围 -> 可以.
}
}
}
mut r: &mut 'c S
.
'c
部分和类型一样限制了赋值到 r
的操作.&b
(0x6
) 赋值到 r
是有效的, 但 &a
(0x3
) 无效, 就因为 &b
生存时间大于等于 &c
.let mut b = S(0);
let r = &mut b;
b = S(4); // 无效. 因为 `b` 处于借用态.
print_byte(r);
&b
或 &mut b
捕获, 变量就会被标记为已借用.b
修改地址的内容.&b
或 &mut b
在上下文中不再使用, 原始绑定 b
将会恢复可用.fn f(x: &S, y:&S) -> &u8 { ... }
let b = S(1);
let c = S(2);
let r = f(&b, &c);
let b = S(1);
let c = S(2);
let r = f(&b, &c);
let a = b; // 这样做可行吗?
let a = c; // 谁才是真正被借用的?
print_byte(r);
f
只能返回一个地址, 所以并不是所有情况下 b
和 c
都需要保持锁定态.fn f<'b, 'c>(x: &'b S, y: &'c S) -> &'c u8 { ... }
let b = S(1);
let c = S(2);
let r = f(&b, &c); // 我们知道返回的引用是基于 `c` 的, 它必须保持锁定;
// 然而 `b` 却是可以自由移动的.
let a = b;
print_byte(r);
'c
).'c
的地址允许被赋值.'b
, 'c
会基于开发者给出的借用态变量被编译器透明地指派给调用方.b
或 c
的 作用域 (可能是从初始化到结束之间的代码行), 但仅有一个最小子集可以作为该作用域的 生命周期, 即基于 b
和 c
需要借用于该调用和保存结果的最少代码行.f
被 'c: 'b
替代, 仍不能区分出来的情况下, 两个都会保持锁定.let mut c = S(2);
let r = f(&c);
let s = r;
// <- 不是这里, `s` 会延长 `c` 的锁定时间.
print_byte(s);
let a = c; // <- 是这里, 不再使用 `r` 和 `s`.
↕️ 点击展开用例
通用数据类型的内存表示.
语言核心内建的必要类型.
u8
, i8
u16
, i16
u32
, i32
u64
, i64
u128
, i128
f32
f64
usize
, isize
ptr
一致.
类型 | 最大值 |
---|---|
u8 | 255 |
u16 | 65_535 |
u32 | 4_294_967_295 |
u64 | 18_446_744_073_709_551_615 |
u128 | 340_282_366_920_938_463_463_374_607_431_768_211_455 |
usize | 取决于平台指针大小, 可以是 u16 , u32 , 或 u64 . |
类型 | 最大值 |
---|---|
i8 | 127 |
i16 | 32_767 |
i32 | 2_147_483_647 |
i64 | 9_223_372_036_854_775_807 |
i128 | 170_141_183_460_469_231_731_687_303_715_884_105_727 |
isize | 取决于平台指针大小, 可以是 i16 , i32 , 或 i64 . |
类型 | 最小值 |
---|---|
i8 | -128 |
i16 | -32_768 |
i32 | -2_147_483_648 |
i64 | -9_223_372_036_854_775_808 |
i128 | -170_141_183_460_469_231_731_687_303_715_884_105_728 |
isize | 取决于平台指针大小, 可以是 i16 , i32 , 或 i64 . |
f32
的位表示*:
S
E
E
E
E
E
E
E
E
F
F
F
F
F
F
F
F
F
F
F
F
F
F
F
F
F
F
F
F
F
F
F
说明:
f32 | S (1) | E (8) | F (23) | 值 |
---|---|---|---|---|
规格化数 | ± | 1 to 254 | 任意 | ±(1.F)2 * 2E-127 |
非规格化数 | ± | 0 | 非零 | ±(0.F)2 * 2-126 |
零 | ± | 0 | 0 | ±0 |
无穷大 | ± | 255 | 0 | ±∞ |
NaN | ± | 255 | 非零 | NaN |
同样, 对于 f64
类型, 这将类似于:
f64 | S (1) | E (11) | F (52) | 值 |
---|---|---|---|---|
规格化数 | ± | 1 to 2046 | 任意 | ±(1.F)2 * 2E-1023 |
非规格化数 | ± | 0 | 非零 | ±(0.F)2 * 2-1022 |
零 | ± | 0 | 0 | ±0 |
无穷大 | ± | 2047 | 0 | ±∞ |
NaN | ± | 2047 | 非零 | NaN |
转换1 | 结果 | 说明 |
---|---|---|
3.9_f32 as u8 | 3 | 截断, 请优先使用 x.round() . |
314_f32 as u8 | 255 | 采用最接近的可用数字. |
f32::INFINITY as u8 | 255 | 同上, 但会把 INFINITY 当做一个 真正的 大数. |
f32::NAN as u8 | 0 | - |
_314 as u8 | 58 | 截断多余的位. |
_200 as i8 | 56 | - |
_257 as i8 | -1 | - |
操作1 | 结果 | 说明 |
---|---|---|
200_u8 / 0_u8 | 编译错误. | - |
200_u8 / _0 d | Panic. | 由于除以 0, 该计算会 panic. |
200_u8 / _0 r | Panic. | 同上. |
200_u8 + 200_u8 | 编译错误. | - |
200_u8 + _200 d | Panic. | 考虑换用 checked_ , wrapping_ 等方法 STD |
200_u8 + _200 r | 144 | 在 release 模式下会溢出. |
1_u8 / 2_u8 | 0 | 整数除法会截断. |
0.8_f32 + 0.1_f32 | 0.90000004 | - |
1.0_f32 / 0.0_f32 | f32::INFINITY | - |
0.0_f32 / 0.0_f32 | f32::NAN | - |
x < f32::NAN | false | NAN 的比较结果永远为假. |
x > f32::NAN | false | - |
f32::NAN == f32::NAN | false | - |
1表达式 _100
表示可能包含 100
值的任何内容, 例如 100_i32
, 但对编译器是不透明的.
d 调试版本.
r 发布版本.
char
str
U
T
F
-
8
&str
代替. 类型 | 描述 |
---|---|
char | 总是为 4 字节, 且仅包含一个 Unicode 标量值🔗. |
str | 未知长度的 u8 数组保证保存 UTF-8 编码的码位. |
字符 | 描述 |
---|---|
let c = 'a'; | 通常一个 char (Unicode 标量) 就是你直觉上认为的 字符. |
let c = '❤'; | 它可以持有很多 Unicode 符号. |
let c = '❤️'; | 但并不总是如此. 比如一个 emoji 是由两个 char (参见编码) 组成的, 并不能🛑存在 c 里.1 |
c = 0xffff_ffff; | 字符也不允许🛑用一个随便的比特模式就表示了. |
字符串 | 描述 |
---|---|
let s = "a"; | 通常并不会直接使用 str , 而是像这里的 s 一样通过 &str 访问. |
let s = "❤❤️"; | 可以存储任意长度的文本, 但很难进行索引. |
let s = "I ❤ Rust";
let t = "I ❤️ Rust";
变体 | 内存表示2 |
---|---|
s.as_bytes() | 49 20 e2 9d a4 20 52 75 73 74 3 |
s.chars() 1 | 49 00 00 00 20 00 00 00 64 27 00 00 20 00 00 00 52 00 00 00 75 00 00 00 73 00 … |
t.as_bytes() | 49 20 e2 9d a4 ef b8 8f 20 52 75 73 74 4 |
t.chars() 1 | 49 00 00 00 20 00 00 00 64 27 00 00 0f fe 01 00 20 00 00 00 52 00 00 00 75 00 … |
❤
对应一个 Unicode 代码点 (U+2764), 它在 char
中被表示为 64 27 00 00, 但在 str
中则被表示为 UTF-8 编码 e2 9d a4.❤️
其实是由心形 ❤
和 U+FE0F Variation Selector 组成的, 可以看到 t
比 s
拥有更多字符.
💬 尽管上面的
s
和t
是不一样的, 但 Safari 和 Edge 都有把脚注 3 和 4 的心形符号渲染错误的 Bug.
用户定义的基本类型. 它实际的内存布局REF取决于表示法REF, 还有对齐.
T
T
T: ?Sized
T
[T; n]
T
T
T
n
个元素的数组. [T]
T
T
T
Sized
(不携带 len
信息) , &[T]
为参照. ↓struct S;
;
(A, B, C)
A
B
C
B
A
C
#[repr(C)]
) struct S { b: B, c: C }
B
C
C
↦
B
还需注意, 具有完全相同字段的两种类型
A(X, Y)
和B(X, Y)
仍然可以具有不同的布局. 在没有使用#[repr()]
限制其布局表示的情况下, 绝不能使用transmute()
进行类型转换.
这些合并类型存有其一种子类型的值:
enum E { A, B, C }
标签
A
标签
B
标签
C
union { ... }
A
B
C
引用授权了对其他内存空间的安全访问. 裸指针则是不安全 unsafe
的访问.
各自的 mut
类型是相同的.
&'a T
ptr
2/4/8
meta
2/4/8
T
t
的 T
, 'a
. *const T
ptr
2/4/8
meta
2/4/8
许多引用和指针类型可以携带一个额外的字段, 即元数据指针STD. 它可以是目标的元素长度或字节长度, 也可以是指向 vtable 的指针. 带有元数据的指针称为胖指针, 否则称为瘦指针.
&'a T
ptr
2/4/8
T
&'a T
ptr
2/4/8
len
2/4/8
T
T
是 DST struct
(如 S { x: [u8] }
), len
则是动态大小内容的长度.&'a [T]
ptr
2/4/8
len
2/4/8
T
T
'a
的切片引用 [T]
的引用类型) ↑ &[T]
.&'a str
ptr
2/4/8
len
2/4/8
U
T
F
-
8
str
的引用),len
即为字节长度.&'a dyn Trait
ptr
2/4/8
ptr
2/4/8
T
*Drop::drop(&mut T) |
size |
align |
*Trait::f(&T, ...) |
*Trait::g(&T, ...) |
*Drop::drop()
, *Trait::f()
等是它们各自 impl
for T
的指针.闭包是一个临时函数, 定义闭包时, 它会自动管理数据捕获REF环境中访问的内容. 例如:
move |x| x + y.f() + z
Y
Z
|x| x + y.f() + z
ptr
2/4/8
ptr
2/4/8
Y
Z
生成匿名函数
fn
如fc1(C1, X)
orfc2(&C2, X)
. 具体细节取决于捕获类型的属性支持FnOnce
,FnMut
还是Fn
.等.
Rust 标准库为上面提到的基本类型扩展了更多有用的类型, 并定义了一些特殊的语义. 一些通用类型如下:
UnsafeCell<T>
T
Cell<T>
T
T
的RefCell<T>
borrowed
T
T
的动态借用.Cell
是 Send
的,Sync
的.AtomicUsize
usize
2/4/8
Result<T, E>
标签
E
标签
T
Option<T>
标签
标签
T
NonNull
.Box<T>
ptr
2/4/8
meta
2/4/8
T
T
栈代理可能会持有 Box<[T]>
).Vec<T>
ptr
2/4/8
capacity
2/4/8
len
2/4/8
T
T
String
ptr
2/4/8
capacity
2/4/8
len
2/4/8
U
T
F
-
8
String
和 &str
以及 &[char]
的区别.CString
ptr
2/4/8
len
2/4/8
A
B
C
␀
OsString
??
?
?
?
PathBuf
?OsString
?
?
?
?
如果类型 T
不包含 Cell
, 那它也会包含以下 Cell
类型的变体以允许共享实际可变性.
Rc<T>
ptr
2/4/8
meta
2/4/8
strng
2/4/8weak
2/4/8T
T
的所有权. 需要嵌套 Cell
或 RefCell
以允许修改. 它既不是 Send
也不是 Sync
的.Arc<T>
ptr
2/4/8
meta
2/4/8
strng
2/4/8weak
2/4/8T
Send
且 Sync
的, 则允许在线程间共享.Mutex<T>
/ RwLock<T>
ptr
2/4/8poison
2/4/8T
lock
Arc
里以便在线程间共享, Send
且 Sync
的. 这些代码片段很通用但经常容易忘. 详情可以参考 Rust Cookbook 🔗.
用途 | 代码 |
---|---|
连接字符串 (任何实现了 Display ↓ 的类型). 1 '21 | format!("{x}{y}") |
以给定匹配分割字符串. STD 🔗 | s.split(pattern) |
... 以 &str | s.split("abc") |
... 以 char | s.split('/') |
... 以闭包 | s.split(char::is_numeric) |
以空白分割. | s.split_whitespace() |
以换行分割. | s.lines() |
以正则表达式分割.2 | Regex::new(r"\s")?.split("one two three") |
1 会产生内存分配. 如果 x
已经是 String
的情况下可能不是性能的最优解.
2 依赖 regex crate.
用途 | 代码 |
---|---|
创建新文件 | File::create(PATH)? |
同上, 但给出选项 | OpenOptions::new().create(true).write(true).truncate(true).open(PATH)? |
用途 | 代码 |
---|---|
具有变量参数的宏 | macro_rules! var_args { ($($args:expr),*) => {{ }} } |
应用 args , 如多次调用 f . | $( f($args); )* |
用途 | 代码 |
---|---|
清理闭包捕获 | wants_closure({ let c = outer.clone(); move || use_clone(c) }) |
修复 'try ' 闭包内的类型推断 | iter.try_for_each(|x| { Ok::<(), Error>(()) })?; |
当 T 满足 Copy 时, 迭代 并 修改 &mut [T] | Cell::from_mut(mut_slice).as_slice_of_cells() |
给定长度的切片 | &original_slice[offset..][..length] |
确保 trait T 是对象安全的写法 | const _: Option<&dyn T> = None; |
例 | Send * | !Send |
---|---|---|
Sync * | 多数类型 ... Mutex<T> , Arc<T> 1,2 | MutexGuard<T> 1, RwLockReadGuard<T> 1 |
!Sync | Cell<T> 2, RefCell<T> 2 | Rc<T> , &dyn Trait , *const T 3, *mut T 3 |
* T: Send
表示实例 t
可以移动到另一个线程; T: Sync
表示 &t
可以移动到另一个线程.
1 如果 T
为 Sync
.
2 如果 T
为 Send
.
3 如果你要发送一个裸指针, 建议创建新类型 struct Ptr(*const u8)
并 unsafe impl Send for Ptr {}
. 用来保证你 可能 会发送它 (到其他线程).
基础
假设有一个元素类型都为 C
的集合 c
:
c.into_iter()
— 将集合 c
转为一个**Iterator
** STD i
并消费掉* c
. 要求实现 C
的 IntoIterator
STD, 其元素类型取决于 C
. 这是获取迭代器的“标准方式”.c.iter()
— 对某些集合更友好的方法, 返回一个借用迭代器而不消费掉 c
.c.iter_mut()
— 同上, 但返回一个允许修改集合元素的可变借用迭代器.迭代
一旦你获得了一个 i
:
i.next()
— 如果下一个元素 c
存在则返回 Some(x)
, 否则返回 None
表示结束.循环
for x in c {}
— 语法糖, 相当于调用 c.into_iter()
并且循环 i
直到它变为 None
.* 当类型是 Copy
的时, 迭代器会看起来并没有消费掉 c
。比如, 调用 (&c).into_iter()
会在 &c
上调用 .into_iter()
(会消费掉该引用并返回一个迭代器), 但本质上并没有去访问 c
.
基础
假设有一集合 struct Collection<T> {}
.
struct IntoIter<T> {}
— 创建一个持有自定义迭代状态 (比如下标) 的结构体.impl Iterator for IntoIter {}
— 实现能够产生元素的 Iterator::next()
.Collection<T>
IntoIter<T>
Iterator
Item = T;
共享和可变迭代器
struct Iter<T> {}
— 创建一个持有 &Collection<T>
的结构体用于共享迭代.struct IterMut<T> {}
— 类似地, 但持有 &mut Collection<T>
用于可变迭代.impl Iterator for Iter<T> {}
— 实现共享迭代.impl Iterator for IterMut<T> {}
— 实现可变迭代.另外, 建议实现如下方法以获取对应迭代器:
Collection::iter(&self) -> Iter
,Collection::iter_mut(&mut self) -> IterMut
.Iter<T>
Iterator
Item = &T;
IterMut<T>
Iterator
Item = &mut T;
实现循环
impl IntoIterator for Collection {}
— 使得 for x in c {}
可用.impl IntoIterator for &Collection {}
— 使得 for x in &c {}
可用.impl IntoIterator for &mut Collection {}
— 使得 for x in &mut c {}
可用.Collection<T>
IntoIterator
Item = T;
To = IntoIter<T>
T
上迭代.&Collection<T>
IntoIterator
Item = &T;
To = Iter<T>
&T
上迭代.&mut Collectn<T>
IntoIterator
Item = &mut T;
To = IterMut<T>
&mut T
上迭代.目前正确的数字转换.
↓ 原始 / 目标 → | u8 … i128 | f32 / f64 | String |
---|---|---|---|
u8 … i128 | u8::try_from(x)? 1 | x as f32 3 | x.to_string() |
f32 / f64 | x as u8 2 | x as f32 | x.to_string() |
String | x.parse::<u8>()? | x.parse::<f32>()? | x |
1 如果是其类型的真子集, from()
将会直接转换, 比如 u32::from(my_u8)
.
2 见下, 这些转换将会截断 (11.9_f32 as u8
得到 11
) 或缩容 (1024_f32 as u8
得到 255
).
3 转换后会重新用二进制位表示 (u64::MAX as f32
) 或产生无穷大 Inf
(u128::MAX as f32
).
下面列出要转换到目标字符串类型的方法:
原始类型 x | 转换方法 |
---|---|
String | x |
CString | x.into_string()? |
OsString | x.to_str()?.to_string() |
PathBuf | x.to_str()?.to_string() |
Vec<u8> 1 | String::from_utf8(x)? |
&str | x.to_string() i |
&CStr | x.to_str()?.to_string() |
&OsStr | x.to_str()?.to_string() |
&Path | x.to_str()?.to_string() |
&[u8] 1 | String::from_utf8_lossy(x).to_string() |
原始类型 x | 转换方法 |
---|---|
String | CString::new(x)? |
CString | x |
OsString 2 | CString::new(x.to_str()?)? |
PathBuf | CString::new(x.to_str()?)? |
Vec<u8> 1 | CString::new(x)? |
&str | CString::new(x)? |
&CStr | x.to_owned() i |
&OsStr 2 | CString::new(x.to_os_string().into_string()?)? |
&Path | CString::new(x.to_str()?)? |
&[u8] 1 | CString::new(Vec::from(x))? |
*mut c_char 3 | unsafe { CString::from_raw(x) } |
原始类型 x | 转换方法 |
---|---|
String | OsString::from(x) i |
CString | OsString::from(x.to_str()?) |
OsString | x |
PathBuf | x.into_os_string() |
Vec<u8> 1 | ? |
&str | OsString::from(x) i |
&CStr | OsString::from(x.to_str()?) |
&OsStr | OsString::from(x) i |
&Path | x.as_os_str().to_owned() |
&[u8] 1 | ? |
原始类型 x | 转换方法 |
---|---|
String | PathBuf::from(x) i |
CString | PathBuf::from(x.to_str()?) |
OsString | PathBuf::from(x) i |
PathBuf | x |
Vec<u8> 1 | ? |
&str | PathBuf::from(x) i |
&CStr | PathBuf::from(x.to_str()?) |
&OsStr | PathBuf::from(x) i |
&Path | PathBuf::from(x) i |
&[u8] 1 | ? |
原始类型 x | 转换方法 |
---|---|
String | x.into_bytes() |
CString | x.into_bytes() |
OsString | ? |
PathBuf | ? |
Vec<u8> 1 | x |
&str | Vec::from(x.as_bytes()) |
&CStr | Vec::from(x.to_bytes_with_nul()) |
&OsStr | ? |
&Path | ? |
&[u8] 1 | x.to_vec() |
原始类型 x | 转换方法 |
---|---|
String | x.as_str() |
CString | x.to_str()? |
OsString | x.to_str()? |
PathBuf | x.to_str()? |
Vec<u8> 1 | std::str::from_utf8(&x)? |
&str | x |
&CStr | x.to_str()? |
&OsStr | x.to_str()? |
&Path | x.to_str()? |
&[u8] 1 | std::str::from_utf8(x)? |
原始类型 x | 转换方法 |
---|---|
String | CString::new(x)?.as_c_str() |
CString | x.as_c_str() |
OsString 2 | x.to_str()? |
PathBuf | ?,4 |
Vec<u8> 1,5 | CStr::from_bytes_with_nul(&x)? |
&str | ?,4 |
&CStr | x |
&OsStr 2 | ? |
&Path | ? |
&[u8] 1,5 | CStr::from_bytes_with_nul(x)? |
*const c_char 1 | unsafe { CStr::from_ptr(x) } |
原始类型 x | 转换方法 |
---|---|
String | OsStr::new(&x) |
CString | ? |
OsString | x.as_os_str() |
PathBuf | x.as_os_str() |
Vec<u8> 1 | ? |
&str | OsStr::new(x) |
&CStr | ? |
&OsStr | x |
&Path | x.as_os_str() |
&[u8] 1 | ? |
原始类型 x | 转换方法 |
---|---|
String | Path::new(x) r |
CString | Path::new(x.to_str()?) |
OsString | Path::new(x.to_str()?) r |
PathBuf | Path::new(x.to_str()?) r |
Vec<u8> 1 | ? |
&str | Path::new(x) r |
&CStr | Path::new(x.to_str()?) |
&OsStr | Path::new(x) r |
&Path | x |
&[u8] 1 | ? |
原始类型 x | 转换方法 |
---|---|
String | x.as_bytes() |
CString | x.as_bytes() |
OsString | ? |
PathBuf | ? |
Vec<u8> 1 | &x |
&str | x.as_bytes() |
&CStr | x.to_bytes_with_nul() |
&OsStr | x.as_bytes() 2 |
&Path | ? |
&[u8] 1 | x |
目标类型 | 原始类型 x | 转换方法 |
---|---|---|
*const c_char | CString | x.as_ptr() |
i 如果可以推断出类型则可简写为 x.into()
.
r 如果可以推断出类型则可简写为 x.as_ref()
.
1 该调用应当也必然为 unsafe
的, 请确保原始数据是对应字符串类型的有效表示 (比如 String
必须是 UTF-8 编码). 🔗
2 仅在某些平台上 std::os::<your_os>::ffi::OsStrExt
支持通过辅助方法获取底层 OsStr
的原始 &[u8]
表示. 例如:
use std::os::unix::ffi::OsStrExt;
let bytes: &[u8] = my_os_str.as_bytes();
CString::new(bytes)?
3 c_char
必须由前一个 CString
生成. 如果是从 FFI 来的则换用 &CStr
.
4 如果没有结尾 0x0
的话是没法简单地转换为 x
的. 最好的办法是通过 CString
转一道.
5 必须保证数组以 0x0
结束.
将类型转换为 String
或输出出来.
Rust 拥有一系列将类型转化为字符串输出的 API, 统称为 格式化 宏:
宏 | 输出 | 说明 |
---|---|---|
format!(fmt) | String | 全功能的“转为 String ”. |
print!(fmt) | 控制台 | 写到标准输出. |
println!(fmt) | 控制台 | 写到标准输出. |
eprint!(fmt) | 控制台 | 写到标准错误输出. |
eprintln!(fmt) | 控制台 | 写到标准错误输出. |
write!(dst, fmt) | 缓冲区 | 别忘了要引入 use std::io::Write; |
writeln!(dst, fmt) | 缓冲区 | 别忘了要引入 use std::io::Write; |
方法 | 说明 |
---|---|
x.to_string() STD | 产生 String , 对每个 Display 类型都作了实现. |
这里 fmt
是个类似于 "hello {}"
字符串字面量, 它可以指定输出 (参见“格式化”) 和附加参数.
这里列出了在 format!
和类似命令中, 通过 trait Display
"{}"
STD 或 Debug
"{:?}"
STD 实现的类型转换 (并不全面):
类型 | 实现 | |
---|---|---|
String | Debug, Display | |
CString | Debug | |
OsString | Debug | |
PathBuf | Debug | |
Vec<u8> | Debug | |
&str | Debug, Display | |
&CStr | Debug | |
&OsStr | Debug | |
&Path | Debug | |
&[u8] | Debug | |
bool | Debug, Display | |
char | Debug, Display | |
u8 … i128 | Debug, Display | |
f32 , f64 | Debug, Display | |
! | Debug, Display | |
() | Debug |
简而言之, Debug
打印出详细信息; 而 特殊 类型需要特别指定如何转换到 ↑ Display
.
格式化宏中的各参数指示器可以是 {}
, {argument}
或后续下述基本语法:
{ [argument] ':' [[fill] align] [sign] ['#'] [width [$]] ['.' precision [$]] [type] }
元素 | 说明 |
---|---|
argument | 数字 (0 , 1 , ...), 参数 '21 或名称,'18 如 print!("{x}") . |
fill | 当指定 width 时该字符串将用于填充空白 (如 0 ). |
align | 当指定宽度时表示左 (< ), 中 (^ ), 右 (> ). |
sign | Can be + for sign to always be printed. |
# | 增强格式化, 如更美观的 Debug STD 格式化 ? 或十六进制前导符 0x . |
width | 最小宽度 (≥ 0), 用 fill 填充 (默认为空格). 如果以 0 开头则用 0 填充. |
precision | 数字类型的十进制位数 (≥ 0), 或非数值类型的最大宽度. |
$ | 将 width 或 precision 解释为参数标识符以允许动态格式化. |
type | Debug STD (? ) 格式化, 十六进制 (x ), 二进制 (b ), 八进制 (o ), 指针 (p ), 科学计数法 (e )... 见此. |
格式举例 | 说明 |
---|---|
{} | 使用 Display 打印下一个参数.STD |
{x} | 同上, 但使用作用域中的 x . '21 |
{:?} | 使用 Debug 打印下一个参数.STD |
{2:#?} | 用 Debug STD 格式化美观打印第三个参数. |
{val:^2$} | 将参数 val 居中, 其宽度由第三个参数指定. |
{:<10.3} | 以宽度 10 进行左对齐, 小数位数是 3. |
{val:#x} | 用十六进制格式化 val 参数, 并带有前导 0x (x 的增强格式). |
基本项目布局, 以及 cargo
常用的文件和目录. ↓
项目 | 代码 |
---|---|
📁 .cargo/ | 项目本地 cargo 配置, 可以包含 config.toml . 🔗 🝖 |
📁 benches/ | 存放该 crate 的性能测试, 通过 cargo bench 运行, 默认要求 nightly. * 🚧 |
📁 examples/ | 使用该 crate 的例程, 其中的代码视该 crate 层级如用户. |
my_example.rs | 每个独立的例程可以通过 cargo run --example my_example 来运行. |
📁 src/ | 项目实际源代码. |
main.rs | 应用程序默认入口, 即 cargo run 运行的内容. |
lib.rs | 库默认入口. 即 my_crate::f() 对应查找的内容. |
📁 src/bin/ | 额外的二进制程序, 在库项目中也可以有. |
x.rs | 二进制程序可通过 cargo run --bin x 来运行. |
📁 tests/ | 集成测试, 通过 cargo test 调用. 单元测试则通常直接放在 src/ 的文件里. |
.rustfmt.toml | 自定义 cargo fmt 的运行方式. |
.clippy.toml | 对特定 clippy lint 的特殊配置, 通过 cargo clippy 调用 🝖 |
build.rs | 预编译脚本, 🔗 当编译 C/FFI 等时常用. |
Cargo.toml | 主项目清单, 🔗 定义了依赖和架构等. |
Cargo.lock | 用于可重复构建的依赖详情, 对于应用程序建议加入 git 版本控制, 但库不需要. |
rust-toolchain.toml | 定义项目的工具链覆盖🔗 (频道, 组件, 目标). |
* stable 可以考虑 Criterion.
各种不同入口的最简单样例如下:
// src/main.rs (默认应用程序入口点)
fn main() {
println!("Hello, world!");
}
// src/lib.rs (默认库入口点)
pub fn f() {} // 根下的公共条目, 可被外部访问.
mod m {
pub fn g() {} // 根下非公开 (`m` 不公开),
} // 所以 crate 外不可访问.
// src/my_module.rs (项目中任意文件)
fn f() -> u32 { 0 }
#[cfg(test)]
mod test {
use super::f; // 需要从父模块导入.
// 可以访问非公共成员.
#[test]
fn ff() {
assert_eq!(f(), 0);
}
}
// tests/sample.rs (例程测试样例)
#[test]
fn my_sample() {
assert_eq!(my_crate::f(), 123); // 集成和性能测试对 crate 的依赖
} // 与依赖第三方库是一样的. 因此仅可访问公开项.
// benches/sample.rs (性能测试样例)
#![feature(test)] // #[bench] 依然是实验性的
extern crate test; // 出于某些原因在 '18 版本仍需要.
// 虽然通常情况下可能不需要.
use test::{black_box, Bencher};
#[bench]
fn my_algo(b: &mut Bencher) {
b.iter(|| black_box(my_crate::f())); // `black_box` 防止 `f` 被优化掉.
}
// build.rs (预编译脚本样例)
fn main() {
// 通过 env 环境变量获取编译目标; 也可使用 `#[cfg(...)]`.
let target_os = env::var("CARGO_CFG_TARGET_OS");
}
*环境变量详见该表.
// src/lib.rs (过程宏默认入口)
extern crate proc_macro; // 需要显式引入.
use proc_macro::TokenStream;
#[proc_macro_attribute] // 此时可以通过 `#[my_attribute]` 使用
pub fn my_attribute(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
// Cargo.toml
[package]
name = "my_crate"
version = "0.1.0"
[lib]
proc-macro = true
模块树和导入规则:
Rust 有如下三种命名空间:
命名空间 类型 | 命名空间 函数 | 命名空间 宏 |
---|---|---|
mod X {} |
fn X() {} |
macro_rules! X { ... } |
X (crate) |
const X: u8 = 1; |
|
trait X {} |
static X: u8 = 1; |
|
enum X {} |
|
|
union X {} |
|
|
struct X {} |
|
|
struct X; 1 |
||
struct X(); 1 |
1 既可以算作 类型 也算作 函数.
enum X {}
和 fn X() {}
冲突struct X;
和 const X
不冲突use my_mod::X;
时, 所有叫作 X
的项都会被导入.出于存在名称转换 (如
fn
何mod
都会被转为小写), 以及 常识 (开发者不太会命名多个X
) 的考虑, 在多数 crate 中通常并不需要担心这些 情况. 但在设计宏的时候, 这是需要考虑的一点.
常用命令行工具.
命令 | 说明 |
---|---|
cargo init | 基于最新的版本创建新项目. |
cargo build | 调试模式构建项目 (--release 启用所有优化). |
cargo check | 检查项目是否通过编译 (比构建更快)). |
cargo test | 运行项目测试用例. |
cargo doc --open | 生成并打开代码和相关依赖的本地文档. |
cargo run | 编译出二进制文件并运行 (main.rs). |
cargo run --bin b | 运行二进制程序 b . 统一 feature 和其他依赖 (可能会产生冲突). |
cargo run -p w | 在子工作空间中 w 运行主程序. 对待 feature 可能更如你所愿. |
cargo tree | 显示依赖树. |
cargo +{nightly, stable} ... | 命令使用给定工具链, 如对某些 'nightly only' 的工具. |
cargo +nightly ... | 某些 nightly-only 命令 (如下替换 ... ) |
build -Z timings | 显示哪个 crate 导致你编译那么久, 很有用. 🚧 🔥 |
rustc -- -Zunpretty=expanded | 显示宏展开. 🚧 |
rustup doc | 打开离线 Rust 文档 (包括官方手册), “云”上编程! |
这里 cargo build
表示可以输入 cargo build
或者简写成 cargo b
; --release
表示可以简写成 -r
.
可选的 rustup
组件.
通过 rustup component add [tool]
安装.
更多 cargo 插件可以在这里找到.
🔘 检查目标是否支持.
🔘 安装目标依赖: rustup target install X
.
🔘 安装本地工具链(取决于目标可能需要链接).
应从目标供应商(Google, Apple 等)获取这些资源.也可能不支持本地宿主环境(比如, Windows 不支持 iOS 工具链).
某些工具链需要额外的构建步骤 (比如 Android 的 make-standalone-toolchain.sh
).
🔘 修改 ~/.cargo/config.toml
如下:
[target.aarch64-linux-android]
linker = "[PATH_TO_TOOLCHAIN]/aarch64-linux-android/bin/aarch64-linux-android-clang"
或者
[target.aarch64-linux-android]
linker = "C:/[PATH_TO_TOOLCHAIN]/prebuilt/windows-x86_64/bin/aarch64-linux-android21-clang.cmd"
🔘 设置环境变量 (可选, 编译器不报错则可以跳过):
set CC=C:\[PATH_TO_TOOLCHAIN]\prebuilt\windows-x86_64\bin\aarch64-linux-android21-clang.cmd
set CXX=C:\[PATH_TO_TOOLCHAIN]\prebuilt\windows-x86_64\bin\aarch64-linux-android21-clang.cmd
set AR=C:\[PATH_TO_TOOLCHAIN]\prebuilt\windows-x86_64\bin\aarch64-linux-android-ar.exe
...
如何设置取决于编辑器提示, 并非所有步骤都是必须的.
某些平台和配置可能对路径表示极其敏感 (比如)
\
和/
).
✔️ 通过 cargo build --target=X
编译.
源代码中用于工具链或预处理的内嵌的特殊标识符.
声明BK
宏 BK
EX REF 使用 macro_rules!
:
写法 | 说明 |
---|---|
$x:ty | 宏捕获 (此处捕获一个类型). |
$x:item | 项, 比如一个函数, 结构体或模块等. |
$x:block | 语句或表达式块 {} , 如 { let x = 5; } |
$x:stmt | 语句, 如 let x = 1 + 1; , String::new(); 或 vec![]; |
$x:expr | 表达式, 如 x , 1 + 1 , String::new() 或 vec![] |
$x:pat | 模式, 如 Some(t) , (17, 'a') 或 _ . |
$x:ty | 类型, 如 String , usize 或 Vec<u8> . |
$x:ident | 标识符, 比如在 let x = 0; 中标识符是 x . |
$x:path | 路径 (如 foo , ::std::mem::replace , transmute::<_, int> ). |
$x:literal | 字面量 (如 3 , "foo" , b"bar" 等.). |
$x:lifetime | 生命周期 (如 'a , 'static 等.). |
$x:meta | 元项; 用在 #[...] 和 #![...] 属性声明里. |
$x:vis | 可见修饰符; pub , pub(crate) 等. |
$x:tt | 单个 token 树, 详情见此. |
$crate | 特殊保留变量, 宏定义所在的 crate. ? |
写法 | 说明 |
---|---|
```...``` | 包含一个文档测试 (文档代码通过 cargo test 运行). |
```X,Y ...``` | 同上, 但包含可选项 X , Y 如下 ... |
rust | 明确该测试是由 Rust 编写的; 可以通过 Rust 工具链解析. |
- | 编译测试. 运行测试. 当 panic 时失败. 默认行为. |
should_panic | 编译测试. 运行测试. 执行应当 panic. 否则测试失败. |
no_run | 编译测试. 编译失败则测试失败, 不会运行测试. |
compile_fail | 编译测试. 但如果代码 能够 通过编译则失败. |
ignore | 不要编译. 不要运行. 忽略. |
edition2018 | 在 Rust '18 版本下运行; 默认是 '15. |
# | 文档中注释某行 (``` # use x::hidden; ``` ). |
[`S`] | 创建一个链接指向结构体, 枚举, trait, 函数, … 的 S . |
[`S`](crate::S) | 可以使用 Markdown 语法指定链接路径. |
这些属性对整个 crate 或应用程序都生效:
外部可选项 | 作用 | 说明 |
---|---|---|
#![no_std] | C | 不自动引入 std STD ; 而使用 core STD . REF |
#![no_implicit_prelude] | CM | 不添加 prelude STD, 需要手动引入 None , Vec 等 REF |
#![no_main] | C | 不触发应用程序中的 main() , 允许自定义启动. REF |
内部可选项 | 作用 | 说明 |
---|---|---|
#![feature(a, b, c)] | C | 依赖于某个永远无法被稳定下来的特性, 参见 Unstable Book. 🚧 |
构建选项 | 作用 | 说明 |
---|---|---|
#![windows_subsystem = "x"] | C | 在 Windows 上创建 console 或 windows 应用程序. REF 🝖 |
#![crate_name = "x"] | C | 当不使用 cargo 时指定当前 crate 名. ? REF 🝖 |
#![crate_type = "bin"] | C | 指定当前 crate 类型 (bin , lib , dylib , cdylib , ...). REF 🝖 |
#![recursion_limit = "123"] | C | 设置解引用和宏展开等的 编译期 递归限制 REF 🝖 |
#![type_length_limit = "456"] | C | 限制类型替换的最大数量. REF 🝖 |
Handler | 作用 | 说明 |
---|---|---|
#[panic_handler] | F | 使函数 fn f(&PanicInfo) -> ! 作为 panic handler. REF |
#[global_allocator] | S | 标记静态实例. GlobalAlloc STD 全局分配器. REF |
这些属性主要用于控制相关代码:
开发者体验 | 作用 | 说明 |
---|---|---|
#[non_exhaustive] | T | 标记 struct 或 enum 未来有可能发生变更. REF |
#[path = "x.rs"] | M | 从非标准文件中获取模块. REF |
代码生成 | 作用 | 声明 |
---|---|---|
#[inline] | F | 建议编译器将函数调用编译为内嵌代码. REF |
#[inline(always)] | F | 要求编译器必须将此函数调用内嵌. REF |
#[inline(never)] | F | 告诉编译器即便该函数可以内嵌也不要这么做. REF |
#[cold] | F | 标记该函数可能并不需要被调用. REF |
#[target_feature(enable="x")] | F | 启用 unsafe fn 下支持的某些 CPU 特性 (如 avx2 ). REF |
#[track_caller] | F | 允许 fn 追溯调用者 caller STD 已获得更详细的 panic 信息. REF |
#[repr(X)] 1 | T | 用另一种指定的表示法来替换 rust REF 默认的: |
#[repr(C)] | T | 使用兼容 C (当 FFI) 且可预测的 (当 transmute ) 内存布局. REF |
#[repr(C, u8)] | enum | 使得该 enum 变体以指定类型表示. REF |
#[repr(transparent)] | T | 使得单元素类型内存布局与其内部字段一致. REF |
#[repr(packed(1))] | T | 结构体及其字段向低位对齐, 可能会 UB. REF |
#[repr(align(8))] | T | 结构体对齐提升, 比如用于 SIMD 类型. REF |
1 某些标识装饰器可以合并在一起写, 如 #[repr(C, packed(1))]
.
链接 | 作用 | 说明 |
---|---|---|
#[no_mangle] | * | 使该项编译后如其名, 不添加乱七八糟的字符. REF |
#[no_link] | X | 当仅使用宏时不链接 extern crate . REF |
#[link(name="x", kind="y")] | X | 链接本地库, 表明符号表将从这里查找. REF |
#[link_name = "foo"] | F | 结息 extern fn 用的符号名. REF |
#[link_section = ".sample"] | FS | 指定对象文件的段名. REF |
#[export_name = "foo"] | FS | 将 fn 或 static 以别名导出. REF |
#[used] | S | 不要优化掉看似未使用过的 static 变量. REF |
Rust 工具链利用这些属性提升代码质量:
代码模式 | 作用 | 说明 |
---|---|---|
#[allow(X)] | * | 让 rustc 或 clippy ... 允许 X 可能导致的警告. REF |
#[warn(X)] 1 | * | ... 产生警告, 结合 clippy lint. 🔥 REF |
#[deny(X)] 1 | * | ... 编译失败. REF |
#[forbid(X)] 1 | * | ... 编译失败并禁用后续的 allow 声明. REF |
#[deprecated = "msg"] | * | 让用户知道你曾经犯了个错误. REF |
#[must_use = "msg"] | FTX | 让编译器检查返回值确被调用者 处理过 了. 🔥 REF |
1 关于在 crate 中什么是 最佳实践 上有过不少争论. 通常多人活跃维护的 crate 可能会提供更激进的 deny
或 forbid
lint; 不定期更新的项目则可能只标记一个 warn
(不保证未来的编译器或者 clippy
不会突然对此产生警告).
测试 | 作用 | 说明 |
---|---|---|
#[test] | F | 标记该函数为测试, 通过 cargo test 运行. 🔥 REF |
#[ignore = "msg"] | F | 编译但目前不运行某些 #[test] . REF |
#[should_panic] | F | 该测试必须 panic!() 才算成功. REF |
#[bench] | F | 在 bench/ 中标记该函数为性能测试, 通过 cargo bench 运行. 🚧 REF |
格式化 | 作用 | 说明 |
---|---|---|
#[rustfmt::skip] | * | 防止 cargo fmt 自动清理该项. 🔗 |
#![rustfmt::skip::macros(x)] | CM | ... 防止自动清理宏 x . 🔗 |
#![rustfmt::skip::attributes(x)] | CM | ... 防止自动清理属性 x . 🔗 |
文档 | 作用 | 说明 |
---|---|---|
#[doc = "Explanation"] | * | 与 /// 文档注释效果相同. 🔗 |
#[doc(alias = "other")] | * | 让用户用该别名也能在文档中搜索到该项. 🔗 |
#[doc(hidden)] | * | 在文档中隐藏. 🔗 |
#![doc(html_favicon_url = "")] | C | 设置文档图标 favicon . 🔗 |
#![doc(html_logo_url = "")] | C | 设置文档 Logo. 🔗 |
#![doc(html_playground_url = "")] | C | 用给定服务生成 运行 按钮. 🔗 |
#![doc(html_root_url = "")] | C | 外部 crate 的基础链接. 🔗 |
#![doc(html_no_source)] | C | 生成的文档中不要包含源代码. 🔗 |
这些属性用于创建或者修饰宏:
声明宏 | 作用 | 说明 |
---|---|---|
#[macro_export] | ! | 将 macro_rules! 导出为 pub 到 crate 层级 REF |
#[macro_use] | MX | 让宏得以运行在模块里; 或从 extern crate 导入. REF |
过程宏 | 作用 | 说明 |
---|---|---|
#[proc_macro] | F | 标记 fn 为 函数式 过程宏, 调用方式如 m!() . REF |
#[proc_macro_derive(Foo)] | F | 标记 fn 为 Derive 宏, 调用方式如 #[derive(Foo)] . REF |
#[proc_macro_attribute] | F | 标记 fn 为 属性宏, 调用方式如一个新的 #[x] . REF |
Derive | 作用 | 说明 |
---|---|---|
#[derive(X)] | T | 通过某些过程宏提供 trait X 的 impl . 🔥 REF |
这些属性用于条件编译:
配置属性 | 作用 | 说明 |
---|---|---|
#[cfg(X)] | * | 如果提供了配置 X 则编译. REF |
#[cfg(all(X, Y, Z))] | * | 如果提供了所有配置则编译. REF |
#[cfg(any(X, Y, Z))] | * | 如果提供了任意配置则编译. REF |
#[cfg(not(X))] | * | 如果未提供 X 则编译. REF |
#[cfg_attr(X, foo = "msg")] | * | 如果提供了 X 则标记 #[foo = "msg"] . REF |
⚠️ Note, options can generally be set multiple times, i.e., the same key can show up with multiple values. One can expect
#[cfg(target_feature = "avx")]
和#[cfg(target_feature = "avx2")]
to be true at the same time.
已知选项 | 作用 | 说明 |
---|---|---|
#[cfg(target_arch = "x86_64")] | * | 指定编译目标 CPU 架构. REF |
#[cfg(target_feature = "avx")] | * | 判断某类指令集是否可用. REF |
#[cfg(target_os = "macos")] | * | 运行的目标操作系统. REF |
#[cfg(target_family = "unix")] | * | 运行的某一类目标操作系统. REF |
#[cfg(target_env = "msvc")] | * | 指定如何让操作系统链接 DLL 和函数. REF |
#[cfg(target_endian = "little")] | * | 你优秀的无开销自定义协议失败的主要原因. REF |
#[cfg(target_pointer_width = "64")] | * | 指针位数, 即 usize 和 CPU 字长. REF |
#[cfg(target_vendor = "apple")] | * | 目标设备制造商. REF |
#[cfg(debug_assertions)] | * | 标记为 debug_assert!() 的和类似调试语句将会 panic. REF |
#[cfg(proc_macro)] | * | 当 crate 编译为过程宏时. REF |
#[cfg(test)] | * | 当 cargo test 时编译. 🔥 REF |
#[cfg(feature = "serde")] | * | 当 crate 启用了编译选项 serde 时. 🔥 REF |
预编译脚本可用的环境变量和输出配置.
输入环境 | 说明 🔗 |
---|---|
CARGO_FEATURE_X | 每个启用的 x 都将设置一个这样的环境变量. |
CARGO_FEATURE_SERDE | 如果启用了 serde 特性. |
CARGO_FEATURE_SOME_FEATURE | 如果启用了 some-feature 特性; 横线 - 会转为下划线 _ . |
CARGO_CFG_X | Exposes cfg's; joins mult. opts. by , and converts - to _ . |
CARGO_CFG_TARGET_OS=macos | 如果 target_os 为 macos . |
CARGO_CFG_TARGET_FEATURE=avx,avx2 | 如果 target_feature 设置为了 avx 和 avx2 . |
OUT_DIR | 输出目录. |
TARGET | 编译结果目录. |
HOST | 指定运行该构建脚本的编译器. |
PROFILE | 可以是 debug 或者 release . |
在 build.rs
通过 env::var()?
可以访问. 列表不完整.
输出字符串 | 说明 🔗 |
---|---|
cargo:rerun-if-changed=PATH | (仅当) PATH 变化时运行 build.rs . |
cargo:rerun-if-env-changed=VAR | (仅当) 环境 VAR 变化时运行 build.rs . |
cargo:rustc-link-lib=[KIND=]NAME | 通过 -l 选项链接到本地库. |
cargo:rustc-link-search=[KIND=]PATH | 通过 -L 选项设置本地库搜索路径. |
cargo:rustc-flags=FLAGS | 为编译器添加自定义标识. ? |
cargo:rustc-cfg=KEY[="VALUE"] | 声明给定 cfg 选项以用于后续编译. |
cargo:rustc-env=VAR=VALUE | 声明在 crate 编译期间可以通过 env!() 访问的变量. |
cargo:rustc-cdylib-link-arg=FLAG | 当构建 cdylib 时的连接器标识. |
cargo:warning=MESSAGE | 产生编译器警告. |
在 build.rs
通过 println!()
调用. 列表不完整.
表格列 作用 说明如下:
C
标识作用在 crate 层级上 (常在顶级文件中声明作 #![my_attr]
).
M
标识作用在模块上.
F
标识作用在函数上.
S
标识作用在静态区上.
T
标识作用在类型上.
X
标识某些特殊场景上.
!
标识作用在宏上.
*
标识作用在任意项上.
允许用户 自定义类型 并减少代码重复.
u8
String
Device
类型 | 值 |
---|---|
u8 | { 0u8, 1u8, ..., 255u8 } |
char | { 'a', 'b', ... '🦀' } |
struct S(u8, char) | { (0u8, 'a'), ... (255u8, '🦀') } |
u8
&u8
&mut u8
[u8; 1]
String
u8
, &u8
, &mut u8
类型.t: T
仅接受精确类型 T
的值, 如:
f(0_u8)
不能以 f(&0_u8)
调用,f(&mut my_u8)
不能以 f(&my_u8)
调用,f(0_u8)
不能以 f(0_i8)
调用.确实, 作为类型而言,
0 != 0
(在数学层面)! 在语言层面, 并没有为了你愉快地使用而定义了这样一个相等比较==(0u8, 0u16)
.
类型 | 值 |
---|---|
u8 | { 0u8, 1u8, ..., 255u8 } |
u16 | { 0u16, 1u16, ..., 65_535u16 } |
&u8 | { 0xffaa&u8, 0xffbb&u8, ... } |
&mut u8 | { 0xffaa&mut u8, 0xffbb&mut u8, ... } |
0_i8 as u8
let x: &u8 = &mut 0_u8;
1 从一类值 (如 u8
) 转换和强转到另一类值 (如 u16
) 时有可能会增加 CPU 指令以完成该操作; 一个类型是某一个类型一部分的叫做子类型 (比如 u8
是 u16
的子类型所以 0_u8
和 0_u16
形式一致), 这种转换在编译期间就可以完成. Rust 并不为规则类型实现子类型转换 (0_u8
确实 不同于 0_u16
), 但生命周期却是有的. 🔗
2 安全并不仅是简单的物理保证 (比如 &u8
无法变为 &u128
), 并且告诉我们“历史经验表明这样转换确实会导致编程错误”.
impl S { }
u8
impl { ... }
String
impl { ... }
Port
impl { ... }
impl Port {
fn f() { ... }
}
impl Port {}
的类型 关联 行为有:
Port::new(80)
port.close()
这里的 关联 更像是一个哲学概念而非技术概念, 没什么能阻止你使用
u8::play_sound()
(只要你乐意).
trait T { }
Copy
Clone
Sized
ShowHex
Copy Trait |
---|
Self |
u8 |
u16 |
... |
Clone Trait |
---|
Self |
u8 |
String |
... |
Sized Trait |
---|
Self |
char |
Port |
... |
Self
指向其包含的类型.
trait ShowHex {
// 要求按文档描述实现.
fn as_hex() -> String;
// 由 trait 作者提供.
fn print_hex() {}
}
Copy
trait Copy { }
Copy
是一种标记 trait, 表示 内存可以被按位复制.Sized
Sized
是由编译器提供用于 已知大小 的类型; 类型大小要么已知, 要么未知.impl T for S { }
impl ShowHex for Port { ... }
impl A for B
将添加类型 B
到该 Trait 的成员列表:ShowHex Trait |
---|
Self |
Port |
u8
impl { ... }
Sized
Clone
Copy
Device
impl { ... }
Transport
Port
impl { ... }
Sized
Clone
ShowHex
Eat
Venison
Eat
venison.eat()
接口 (Interface)
Eat
.Venison
时, 他需要决定是否为 Venison
实现 Eat
.Venison
时, Santa 才可以使用由 Eat
定义的行为:// Santa 导入 `Venison` 创建的对象可以 `eat()`.
import food.Venison;
new Venison("rudolph").eat();
Eat
Venison
Venison
+
Eat
venison.eat()
Trait
Eat
.Venison
并决定暂不实现 Eat
(他甚至不知道有 Eat
这么个东西).Venison
添加 Eat
是个好主意.Venison
时需要另外导入 Eat
:// Santa 需要导入 `Venison` 用于创建, 并导入 `Eat` 用于调用 trait 方法.
use food::Venison;
use tasks::Eat;
// 吼吼吼
Venison::new("rudolph").eat();
* 为避免两个人实现不同的 Eat
, Rust 限制了 Alice 和 Bob 能做的事情; 即, 一个 impl Eat for Venison
仅能在 Venison
所在的 crate 或 Eat
所在的 crate 中实现. 这被称作 trait 实现的“孤儿原则”. ?
Vec<>
Vec<u8>
Vec<char>
Vec<u8>
是“字节向量”类型; Vec<char>
是“字符向量”类型, 但 Vec<>
是什么?构造 | 值 |
---|---|
Vec<u8> | { [], [1], [1, 2, 3], ... } |
Vec<char> | { [], ['a'], ['x', 'y', 'z'], ... } |
Vec<> | - |
Vec<>
Vec<>
为非类型, 不占内存, 也不会生成代码.Vec<>
为 类型构造器, 是“模板”或者“创建类型的表单”
Vec<UserType>
时才成为真正的类型.<T>
Vec<T>
[T; 128]
&T
&mut T
S<T>
Vec<>
的参数常作 T
故有 Vec<T>
.T
“类型的变量名”可以特化为 Vec<f32>
, S<u8>
, …类型构造器 | 产生一类 |
---|---|
struct Vec<T> {} | Vec<u8> , Vec<f32> , Vec<Vec<u8>> , ... |
[T; 128] | [u8; 128] , [char; 128] , [Port; 128] ... |
&T | &u8 , &u16 , &str , ... |
// S<> 是个带有参数 T 的类型构造器; 用户可以提供 T 的任意实际类型.
struct S<T> {
x: T
}
// 实际使用中必须为 T 指定实际类型.
fn f() {
let x: S<f32> = S::new(0_f32);
}
[T; N]
and S<const N: usize>
[T; n]
S<const N>
[T; n]
构造出一个有 n
个 T
类型元素的数组.MyArray<T, const N: usize>
.类型构造器 | 产生一类 |
---|---|
[u8; N] | [u8; 0] , [u8; 1] , [u8; 2] , ... |
struct S<const N: usize> {} | S<1> , S<6> , S<123> , ... |
let x: [u8; 4]; // "4 字节数组"
let y: [f32; 16]; // "16 浮点数组"
// `MyArray` 是个需要特定类型 `T` 和特定大小 `N` 的类型构造器.
struct MyArray<T, const N: usize> {
data: [T; N],
}
where T: X
Num<T>
→
Num<u8>
Num<f32>
Num<Cmplx>
u8
Absolute
Dim
Mul
Port
Clone
ShowHex
T
可以为任意类型, 那我们怎么确定能为它实现这么一个 Num<T>
?// 类型仅能由某些实现了 `Absolute` 的 `T` 实现.
struct Num<T> where T: Absolute {
...
}
Absolute Trait |
---|
Self |
u8 |
u16 |
... |
此处我们为该结构体添加了约束. 实践中则最好在 impl 块中添加约束, 详见下文.
where T: X + Y
u8
Absolute
Dim
Mul
f32
Absolute
Mul
char
Cmplx
Absolute
Dim
Mul
DirName
TwoD
Car
DirName
struct S<T>
where
T: Absolute + Dim + Mul + DirName + TwoD
{ ... }
+ X
声明都会减少这里能用的类型.impl<>
impl<T> S<T> where T: Absolute + Dim + Mul {
fn f(&self, x: T) { ... };
}
可以读作:
T
一个实现 (即 impl <T>
部分),Absolute + Dim + Mul
这些 Trait 约束,S<T>
,可以将类如 impl<T> ... {}
的代码看作对一类行为的抽象实现. 尤其使得第三方透明地实例化与类型构造器如何实例化类似:
// 当编译器遇到如下代码时将会
// - 检查 `0` 和 `x` 是否满足 `T` 的要求
// - 创建两个版本的 `f`, 一个给 `char`, 另一个给 `u32`.
// - 并基于“一类实现”来提供
s.f(0_u32);
s.f('x');
impl<T> X for T { ... }
也可以针对多种类型编写针对某“一类的实现”:
// 为任意已经实现过 ToHex 的类型实现 Serialize
impl<T> Serialize for T where T: ToHex { ... }
这称为一揽子实现.
ToHex |
---|
Self |
Port |
Device |
... |
→ 左边的类型总能根据该 impl
实现到右边 →
Serialize Trait |
---|
Self |
u8 |
Port |
... |
这样可以用一种模块化的方法为已经实现了其他接口的给定外部类型提供一种优雅的实现.
Trait<In> { type Out; }
注意某些 Trait 会被“附加”多次, 而有些又只有一次?
Port
From<u8>
From<u16>
Port
Deref
type u8;
为什么?
trait From<I> {}
trait Deref { type O; }
Self
调用的吗?I
(即输入) 和 O
(即输出) 不过是该 Trait 列表的另外一 列 罢了:impl From<u8> for u16 {}
impl From<u16> for u32 {}
impl Deref for Port { type O = u8; }
impl Deref for String { type O = str; }
From | |
---|---|
Self | I |
u16 | u8 |
u32 | u16 |
... |
Deref | |
---|---|
Self | O |
Port | u8 |
String | str |
... |
这里会有点绕,
O
参数必须由输入参数 I
唯一确定,X Y
会表现为一个函数),Self
作为输入.一个更复杂的样例:
trait Complex<I1, I2> {
type O1;
type O2;
}
Complex
,Self
也是输入) 和 2 两个输出, 可以表示为 (Self, I1, I2) => (O1, O2)
Complex | ||||
---|---|---|---|---|
Self [I] | I1 | I2 | O1 | O2 |
Player | u8 | char | f32 | f32 |
EvilMonster | u16 | str | u8 | u8 |
EvilMonster | u16 | String | u8 | u8 |
NiceMonster | u16 | String | u8 | u8 |
NiceMonster 🛑 | u16 | String | u8 | u16 |
(NiceMonster, u16, String)
无效,
因为已经由输出唯一确定了.
A<I>
Car
Car
A<I>
car.a(0_u8)
car.a(0_f32)
B
type O;
Car
Car
B
T = u8;
car.b(0_u8)
car.b(0_f32)
I
参数允许“一类实现”转发给用户 (Santa),O
参数必须由 Trait 实现者确定 (Alice 或 Bob).trait A<I> { }
trait B { type O; }
// 实现者将 (X, u32) 添加到 A.
impl A<u32> for X { }
// 实现者将一类 impl (X, ...) 添加到 A, 用户则可以特化之.
impl<T> A<T> for Y { }
// 实现者必须决定将指定入口 (X, O) 添加到 B.
impl B for X { type O = u32; }
A | |
---|---|
Self | I |
X | u32 |
Y | ... |
T
添加更多成员.
B | |
---|---|
Self | O |
Player | String |
X | u32 |
Self
), 实现者必须预先选择 O
.
Query
vs.
Query<I>
vs.
Query
type O;
vs.
Query<I>
type O;
参数选择取决于 Trait 的作用.
无额外参数
trait Query {
fn search(&self, needle: &str);
}
impl Query for PostgreSQL { ... }
impl Query for Sled { ... }
postgres.search("SELECT ...");
Query
→
PostgreSQL
Query
Sled
Query
Trait 作者假设:
输入参数
trait Query<I> {
fn search(&self, needle: I);
}
impl Query<&str> for PostgreSQL { ... }
impl Query<String> for PostgreSQL { ... }
impl<T> Query<T> for Sled where T: ToU8Slice { ... }
postgres.search("SELECT ...");
postgres.search(input.to_string());
sled.search(file);
Query<I>
→
PostgreSQL
Query<&str>
Query<String>
Sled
Query<T>
T
is ToU8Slice
.Trait 作者假设:
Self
类型提供多个自定义 API 实现,I
类型行为可用.输出参数
trait Query {
type O;
fn search(&self, needle: Self::O);
}
impl Query for PostgreSQL { type O = String; ...}
impl Query for Sled { type O = Vec<u8>; ... }
postgres.search("SELECT ...".to_string());
sled.search(vec![0, 1, 2, 4]);
Query
type O;
→
PostgreSQL
Query
O = String;
Sled
Query
O = Vec<u8>;
Trait 作者假设:
Self
类型自定义 API (但只有一种办法),Self
的自定义.如你所见, 对于函数而言 输入 或 输出 项都 不一定 (除非有必要) 是
I
或O
!
多个输入输出参数
trait Query<I> {
type O;
fn search(&self, needle: I) -> Self::O;
}
impl Query<&str> for PostgreSQL { type O = String; ... }
impl Query<CString> for PostgreSQL { type O = CString; ... }
impl<T> Query<T> for Sled where T: ToU8Slice { type O = Vec<u8>; ... }
postgres.search("SELECT ...").to_uppercase();
sled.search(&[1, 2, 3, 4]).pop();
Query<I>
type O;
→
PostgreSQL
Query<&str>
O = String;
Query<CString>
O = CString;
Sled
Query<T>
O = Vec<u8>;
T
is ToU8Slice
.如上例, 通常 Trait 作者假设:
I
类型行为可用,MostTypes
Sized
vs.
Z
Sized
vs.
str
Sized
[u8]
Sized
dyn Trait
Sized
...
Sized
T
就是 Sized
STD 的, u8
和 &[u8]
是有大小的, [u8]
则不是.Sized
意味着存在 impl Sized for T {}
. 这个会自动实现且也不能由用户实现.Sized
的类型称为 动态大小类型 BK
NOM REF (DST), 有时是 无大小的.示例 | 说明 |
---|---|
struct A { x: u8 } | 类型 A 有大小, 即存在 impl Sized for A , 是最“规则”的类型. |
struct B { x: [u8] } | 因为 [u8] 是 DST, B 就会变成 DST, 即不存在 impl Sized . |
struct C<T> { x: T } | 类型参数 具有 隐式的 T: Sized 约束, 如 C<A> 有效, C<B> 无效. |
struct D<T: ?Sized> { x: T } | 使用 ?Sized REF 会取消大小约束, 即 D<B> 也是有效的. |
struct E; | 类型 E 是零大小的 (也是有确定大小的 Sized ) 且不会耗费内存. |
trait F { fn f(&self); } | Trait 没有 隐式声明 Sized 约束, 即 impl F for B {} 有效. |
trait F: Sized {} | Trait 具有 Sized 父 Trait.↑ |
trait G { fn g(self); } | 对 Self 类似参数 DST impl 仍无效, 因为参数不能进栈. |
?Sized
S<T>
→
S<u8>
S<char>
S<str>
struct S<T> { ... }
T
可为任意确定类型.T: Sized
, 故 S<str>
不可用.T : ?Sized
以取消该默认约束:S<T>
→
S<u8>
S<char>
S<str>
struct S<T> where T: ?Sized { ... }
<'a>
S<'a>
&'a f32
&'a mut u8
'a
(编译器会在方法中提供帮助),Vec<f32>
和 Vec<u8>
是不同的类型, 故记为 S<'p>
和 S<'q>
,S<'a>
的值而不管 S<'b>
(异常: 生命周期的“子类型”关系, 如 'a
长于 'b
).S<'a>
→
S<'auto>
S<'static>
'static
是仅有的 类型空间 中的具名生命周期.// `'a 是这里的自由参数 (用户可以在任意生命周期上使用)
struct S<'a> {
x: &'a u32
}
// 非泛型代码中, 'static 是这里仅有的能使用的具名声明周期.
let a: S<'static>;
// 非泛型代码中我们无需指定 'a 并使得 Rust 通过右值自动推断出 'a.
let b: S;
* 这里有些微妙的不同, 比如显式地创建一个类型为 u32
的实例 0
, 但由于 'static
的例外你并不能创建一个生命周期, 比如 "lines 80 - 100", 编译器会自动帮你完成这些工作. 🔗
Note to self and TODO: that analogy seems somewhat flawed, as if
S<'a>
is toS<'static>
likeS<T>
is toS<u32>
, then'static
would be a type; but then what's the value of that type?
点击展开样例.
A visual overview of types and traits in crates.
u8
u16
f32
bool
char
File
String
Builder
Vec<T>
Vec<T>
Vec<T>
&'a T
&'a T
&'a T
&mut 'a T
&mut 'a T
&mut 'a T
[T; n]
[T; n]
[T; n]
Vec<T>
Vec<T>
f<T>() {}
drop() {}
PI
dbg!
Copy
Deref
type Tgt;
From<T>
From<T>
From<T>
Serialize
Transport
ShowHex
Device
From<u8>
String
Serialize
String
From<u8>
String
From<Port>
Port
From<u8>
From<u16>
Container
Deref
Tgt = u8;
Deref
Tgt = f32;
T
T
T
ShowHex
A walk through the jungle of types, traits, and implementations that (might possibly) exist in your application.
How to get B
when you have A
?
fn f(x: A) -> B {
// How can you obtain B from A?
}
Method | Explanation |
---|---|
Identity | Trivial case, B is exactly A . |
Computation | Create and manipulate instance of B by writing code transforming data. |
Casts | On-demand conversion between types where caution is advised. |
Coercions | Automatic conversion within 'weakening ruleset'.1 |
Subtyping | Automatic conversion within 'same-layout-different-lifetimes ruleset'.1 |
1 While both convert A
to B
, coercions generally link to an unrelated B
(a type "one could reasonably expect to have different methods"),
while subtyping links to a B
differing only in lifetimes.
fn f(x: A) -> B {
x.into()
}
Bread and butter way to get B
from A
. Some traits provide canonical, user-computable type relations:
Trait | Example | Trait implies ... |
---|---|---|
impl From<A> for B {} | a.into() | Obvious, always-valid relation. |
impl TryFrom<A> for B {} | a.try_into()? | Obvious, sometimes-valid relation. |
impl Deref for A {} | *a | A is smart pointer carrying B ; also enables coercions. |
impl AsRef<B> for A {} | a.as_ref() | A can be viewed as B . |
impl AsMut<B> for A {} | a.as_mut() | A can be mutably viewed as B . |
impl Borrow<B> for A {} | a.borrow() | A has borrowed analog B (behaving same under Eq , ...). |
impl ToOwned for A { ... } | a.to_owned() | A has owned analog B . |
fn f(x: A) -> B {
x as B
}
Convert types with keyword as
if conversion relatively obvious but might cause issues. NOM
A | B | 示例 | 说明 |
---|---|---|---|
Ptr | Ptr | device_ptr as *const u8 | If *A , *B are Sized . |
Ptr | Integer | device_ptr as usize | |
Integer | Ptr | my_usize as *const Device | |
Number | Number | my_u8 as u16 | Often surprising behavior. ↑ |
enum w/o fields | Integer | E::A as u8 | |
bool | Integer | true as u8 | |
char | Integer | 'A' as u8 | |
&[T; N] | *const T | my_ref as *const u8 | |
fn(...) | Ptr | f as *const u8 | If Ptr is Sized . |
fn(...) | Integer | f as usize |
Where Ptr
, Integer
, Number
are just used for brevity and actually mean:
Ptr
any *const T
or *mut T
;Integer
any countable u8
... i128
;Number
any Integer
, f32
, f64
.Opinion 💬 — Casts, esp.
Number
-Number
, can easily go wrong. If you are concerned with correctness, consider more explicit methods instead.
fn f(x: A) -> B {
x
}
Automatically weaken type A
to B
; types can be substantially1 different. NOM
A | B | Explanation |
---|---|---|
&mut T | &T | Pointer weakening. |
&mut T | *mut T | - |
&T | *const T | - |
*mut T | *const T | - |
&T | &U | Deref, if impl Deref<Target=U> for T . |
T | U | Unsizing, if impl CoerceUnsized<U> for T .2 🚧 |
T | V | Transitivity, if T coerces to U and U to V . |
|x| x + x | fn(u8) -> u8 | Non-capturing closure, to equivalent fn pointer. |
1 Substantially meaning one can regularly expect a coercion result B
to be an entirely different type (i.e., have entirely different methods) than the original type A
.
2 Does not quite work in example above as unsized can't be on stack; imagine f(x: &A) -> &B
instead. Unsizing works by default for:
[T; n]
to [T]
T
to dyn Trait
if impl Trait for T {}
.Foo<..., T, ...>
to Foo<..., U, ...>
under arcane 🔗 circumstances.fn f(x: A) -> B {
x
}
Automatically converts A
to B
for types only differing in lifetimes NOM - subtyping examples:
A(subtype) | B(supertype) | Explanation |
---|---|---|
&'static u8 | &'a u8 | Valid, forever-pointer is also transient-pointer. |
&'a u8 | &'static u8 | 🛑 Invalid, transient should not be forever. |
&'a &'b u8 | &'a &'b u8 | Valid, same thing. But now things get interesting. Read on. |
&'a &'static u8 | &'a &'b u8 | Valid, &'static u8 is also &'b u8 ; covariant inside & . |
&'a mut &'static u8 | &'a mut &'b u8 | 🛑 Invalid and surprising; invariant inside &mut . |
Box<&'a static> | Box<&'a u8> | Valid, box with forever is also box with transient; covariant. |
Box<&'a u8> | Box<&'static u8> | 🛑 Invalid, box with transient may not be with forever. |
Box<&'a mut u8> | Box<&'a u8> | 🛑 ⚡ Invalid, see table below, &mut u8 never was a &u8 . |
Cell<&'a static> | Cell<&'a u8> | 🛑 Invalid, cells are never something else; invariant. |
fn(&'static u8) | fn(&'u8 u8) | 🛑 If fn needs forever it may choke on transients; contravar. |
fn(&'a u8) | fn(&'static u8) | But sth. that eats transients can be(!) sth. that eats forevers. |
for<'r> fn(&'r u8) | fn(&'a u8) | Higher-ranked type for<'r> fn(&'r u8) is also fn(&'a u8). |
In contrast, these are not🛑 examples of subtyping:
A | B | Explanation |
---|---|---|
u16 | u8 | 🛑 Obviously invalid; u16 should never automatically be u8 . |
u8 | u16 | 🛑 Invalid by design; types w. different data still never subtype even if they could. |
&'a mut u8 | &'a u8 | 🛑 Trojan horse, not subtyping; but coercion (still works, just not subtyping). |
fn f(x: A) -> B {
x
}
Automatically converts A
to B
for types only differing in lifetimes NOM - subtyping variance rules:
'a
that outlives a shorter 'b
is a subtype of 'b
.'static
is subtype of all other lifetimes 'a
.&'a T
) are subtypes of each other the following variance table is used:Construct1 | 'a | T | U |
---|---|---|---|
&'a T | covariant | covariant | |
&'a mut T | covariant | invariant | |
Box<T> | covariant | ||
Cell<T> | invariant | ||
fn(T) -> U | contravariant | covariant | |
*const T | covariant | ||
*mut T | invariant |
Covariant means if A
is subtype of B
, then T[A]
is subtype of T[B]
.
Contravariant means if A
is subtype of B
, then T[B]
is subtype of T[A]
.
Invariant means even if A
is subtype of B
, neither T[A]
nor T[B]
will be subtype of the other.
1 Compounds like struct S<T> {}
obtain variance through their used fields, usually becoming invariant if multiple variances are mixed.
💡 In other words, 'regular' types are never subtypes of each other (e.g.,
u8
is not subtype ofu16
), and aBox<u32>
would never be sub- or supertype of anything. However, generally aBox<A>
, can be subtype ofBox<B>
(via covariance) ifA
is a subtype ofB
, which can only happen ifA
andB
are 'sort of the same type that only differed in lifetimes', e.g.,A
being&'static u32
andB
being&'a u32
.
Java 或 C 的使用者需要转换下思维:
习语 | 代码 |
---|---|
用表达式思考 | x = if x { a } else { b }; |
x = loop { break 5 }; | |
fn f() -> u32 { 0 } | |
用迭代器思考 | (1..10).map(f).collect() |
names.iter().filter(|x| x.starts_with("A")) | |
用 ? 捕获异常 | x = try_something()?; |
get_option()?.run()? | |
使用强类型 | enum E { Invalid, Valid { ... } } 之于 ERROR_INVALID = -1 |
enum E { Visible, Hidden } 之于 visible: bool | |
struct Charge(f32) 之于 f32 | |
提供生成器 | Car::new("Model T").hp(20).run(); |
分离实现 | 泛型 S<T> 可以对每个 T 都有不同的实现. |
Rust 没有面向对象, 但通过 impl 可以实现特化. | |
Unsafe | 尽量避免 unsafe {} , 因为总是会有更快更安全的解决方案的.除了 FFI. |
实现 Trait | #[derive(Debug, Copy, ...)] .根据需要实现 impl . |
工具链 | 利用 clippy 可以提升代码质量. |
用 rustfmt 格式化可以帮助别人看懂你的代码. | |
添加单元测试 BK
(#[test] ), 确保代码正常运行. | |
添加文档测试 BK
(``` my_api::f() ``` ), 确保文档匹配代码. | |
文档 | 以文档注解的 API 可显示在 docs.rs 上. |
不要忘记在开始加上总结句和例程. | |
如果有这些也加上: Panics, Errors, Safety, Abort 和未定义行为. |
类似于 C# 或 TypeScript 的 async / await, 但又有所不同:
语法 | 说明 |
---|---|
async | Anything declared async always returns an impl Future<Output=_> . STD |
async fn f() {} | Function f returns an impl Future<Output=()> . |
async fn f() -> S {} | Function f returns an impl Future<Output=S> . |
async { x } | Transforms { x } into an impl Future<Output=X> . |
let sm = f(); | Calling f() that is async will not execute f , but produce state machine sm . 1 2 |
sm = async { g() }; | Likewise, does not execute the { g() } block; produces state machine. |
runtime.block_on(sm); | Outside an async {} , schedules sm to actually run. Would execute g() . 3 4 |
sm.await | Inside an async {} , run sm until complete. Yield to runtime if sm not ready. |
1 Technically async
transforms following code into anonymous, compiler-generated state machine type; f()
instantiates that machine.
2 The state machine always impl Future
, possibly Send
& co, depending on types used inside async
.
3 State machine driven by worker thread invoking Future::poll()
via runtime directly, or parent .await
indirectly.
4 Rust doesn't come with runtime, need external crate instead, e.g., async-std or tokio 0.2+. Also, more helpers in futures crate.
对每个 x.await
, 状态机将会通过控制转移到状态机 x
.有时, 由 .await
调用的低级状态机并未就绪, 此时, 工作线程直接返回到运行时, 以使得它可以驱动另一个 Future.一段时间后, 运行时:
sm
/ Future
已析构.async
代码块内部代码的简易流程图如下:
consecutive_code(); consecutive_code(); consecutive_code();
START --------------------> x.await --------------------> y.await --------------------> READY
// ^ ^ ^ Future<Output=X> 就绪 -^
// 由运行时调用 | |
// 或由外部 .await 调用 | 会由另一个线程恢复(下一个最佳可用的),
// | 或者当 Future 已析构时根本不会执行.
// |
// 执行 `x`.若已就绪, 则继续执行.
// 若未就绪, 返回当前线程到运行时.
With the execution flow in mind, some considerations when writing code inside an async
construct:
语法 1 | 说明 |
---|---|
sleep_or_block(); | 显然不对🛑, 当前线程永不终止, 阻碍执行器. |
set_TL(a); x.await; TL(); | 显然不对🛑, await 会由其他线程返回, thread local 无效. |
s.no(); x.await; s.go(); | 可能不对🛑, 如果等待时 Future 被析构, 则 await 不会返回.2 |
Rc::new(); x.await; rc(); | 非 Send 类型拒绝实现 impl Future .兼容性差. |
1 Here we assume s
is any non-local that could temporarily be put into an invalid state;
TL
is any thread local storage, and that the async {}
containing the code is written
without assuming executor specifics.
2 Since Drop is run in any case when Future
is dropped, consider using drop guard that cleans up / fixes application state if it has to be left in bad condition across .await
points.
There is a subtrait relationship Fn
: FnMut
: FnOnce
. That means a closure that
implements Fn
STD also implements FnMut
and FnOnce
. Likewise a closure
that implements FnMut
STD also implements FnOnce
. STD
从调用者的角度来看这意味着:
Signature | Function g can call … | Function g accepts … |
---|---|---|
g<F: FnOnce()>(f: F) | … f() once. | Fn , FnMut , FnOnce |
g<F: FnMut()>(mut f: F) | … f() multiple times. | Fn , FnMut |
g<F: Fn()>(f: F) | … f() multiple times. | Fn |
注意, 对调用者来说, 如何确定 Fn
闭包, 是最为严格的.但是一个包含 Fn
的闭包, 对调用者来説, 是对任意函数都最兼容的.
站在定义闭包的角度来看:
闭包 | 实现* | 说明 |
---|---|---|
|| { moved_s; } | FnOnce | 调用者必须放弃 moved_s 的所有权. |
|| { &mut s; } | FnOnce , FnMut | 允许 g() 改变调用者的局部状态 s . |
|| { &s; } | FnOnce , FnMut , Fn | 可能不会导致状态改变, 但可能会共享和重用 s . |
* Rust 偏向于以索引捕获(在调用者视角上最「兼容」 Fn
的闭包), 但也可以用 move || {}
语法通过复制或者移动捕获相关环境变量..
这会带来如下优势和劣势:
要求 | 优势 | 劣势 |
---|---|---|
F: FnOnce | 容易满足调用者. | 仅用一次, g() 仅会调用 f() 一次. |
F: FnMut | 允许 g() 改变调用者状态. | 调用者不能在 g() 期间重用捕获. |
F: Fn | 可同时存在多个. | 最难由调用者生成. |
Unsafe 导致 unsound, unsound 导致 undefined, undefined 是一切原力的阴暗面.
Unsafe 代码
unsafe
的代码有特权.比如, 解引用裸指针, 或调用其他 unsafe
函数.unsafe
代码自身并非有害, 但危险的是 FFI 使用方或者异常的数据结构.// `x` must always point to race-free, valid, aligned, initialized u8 memory.
unsafe fn unsafe_f(x: *mut u8) {
my_native_lib(x);
}
未定义行为 (UB)
unsafe
代码意味着对编译器的特殊承诺(否则它就不需要是 unsafe
的了).if should_be_true() {
let r: &u8 = unsafe { &*ptr::null() }; // 一旦运行, 整个程序都会处于未定义状态.
} else { // 尽管这一行看似什么都没干, 程序可能两条路径
println!("the spanish inquisition"); // 都运行了, 然后破坏掉数据, 或者发生别的.
}
Unsound 代码
unsafe
代码可能违反上述承诺而产生未定义行为.fn unsound_ref<T>(x: &T) -> &u128 { // Signature looks safe to users. Happens to be
unsafe { mem::transmute(x) } // ok if invoked with an &u128, UB for practically
} // everything else.
负责任地使用 unsafe 💬
- 除非非用不可, 不要使用
unsafe
.- Follow the Nomicon, Unsafe Guidelines, always uphold all safety invariants, and never invoke UB.
- 最小化
unsafe
用例, 封装成易于评审的小的, 优雅的模块.- Never create unsound abstractions; if you can't encapsulate
unsafe
properly, don't do it.- 每个
unsafe
用例应当同时提供关于其安全性的纯文本理由提要.
When updating an API, these changes can break client code.RFC Major changes (🔴) are definitely breaking, while minor changes (🟡) might be breaking:
Crate |
---|
🔴 编写一个 stable 的 crate 但却依赖了 nightly. |
🟡 修改了 Cargo 的功能(比如添加或移除功能) |
模块 |
---|
🔴 重命名, 移动, 移除任何公开项. |
🟡 添加新的公开项, 因为 use your_crate::* 可能会破坏现有代码. |
结构体 |
---|
🔴 当所有字段都为公开时添加私有字段. |
🔴 当没有私有字段时添加公开字段. |
🟡 当至少有一个字段时添加或移除私有字段(在更改前或更改后). |
🟡 将有私有字段(至少有一个字段)的元组结构转换到普通结构, 或反之. |
枚举 |
---|
🔴 Adding new variants; can be mitigated with early #[non_exhaustive] REF |
🔴 Adding new fields to a variant. |
Trait |
---|
🔴 添加非默认项, 将会破坏已有的 impl T for S {} . |
🔴 任何不必要的项签名修改, 都会影响到使用者或者实现方. |
🟡 添加一个默认项, 可能会和另一个 trait 产生歧义. |
🟡 添加默认类型参数. |
Trait |
---|
🔴 实现任何「基本」trait.不去实现一个基本 trait 是一种最基本的承诺. |
🟡 实现任何非基本的 trait, 可能会导致歧义. |
固有实现 |
---|
🟡 添加内部项, 可能会导致客户端倾向于调用这个 trait 的 fn 而导致编译错误. |
类型定义签名 |
---|
🔴 强约束(如 <T> 到 <T: Clone> ). |
🟡 弱约束. |
🟡 添加默认类型参数. |
🟡 泛型归纳. |
函数签名 |
---|
🔴 添加或移除参数. |
🟡 引入新的类型参数. |
🟡 泛型归纳. |
行为更改 |
---|
🔴 / 🟡 改变语义可能不会导致编译器错误, 但可能会使用户产生错误的逻辑. |
这里列出了其他的优秀指南和图表.
备忘清单 | 说明 |
---|---|
Rust Learning⭐ (中文) | 可能是学习 Rust 最好的链接合集. |
Functional Jargon in Rust | Rust 的函数式编程术语解释合集. |
Periodic Table of Types | 解释各种类型和引用是如何联系在一起的. |
Futures | 如何使用 Future. |
Rust Iterator Cheat Sheet | std::iter 和 itertools 的迭代器相关方法总结. |
Type-Based Rust Cheat Sheet | 常见类型和方法表. 可以打印下来挂墙上. |
多数 Rust 资料都由社区开发.
书记 ️📚 | 说明 |
---|---|
The Rust Programming Language (中文) | 《Rust 程序设计语言》, 入门必备. |
API Guidelines | 如何编写符合惯例可复用的 Rust. |
Asynchronous Programming 🚧 | 解释 async 代码, Futures , ... |
Design Patterns | 惯例, 模式和反模式. |
Edition Guide | 与 Rust 2015, Rust 2018 等各版本打交道. |
Guide to Rustc Development | 解释编译器内部如何工作. |
Little Book of Rust Macros | 社区对 Rust 宏的经验总结. |
Reference 🚧 (中文) | 《Rust 参考手册》. |
RFC Book | 已接受的 RFC 文档, 可以看到它们是如何影响语言的. |
Performance Book | 改进速度与内存使用的技术. |
Rust Cookbook | 已被证明是良好实践的例程集. |
Rust in Easy English | 用入门级的英语讲的 Rust, 适合入门, 也适合英语初学者. |
Rust for the Polyglot Programmer | 经验者的指南. |
Rustdoc Book | 如何自定义 cargo doc 和 rustdoc . |
Rustonomicon (中文) | 《Rust 秘典》, 旧称《死灵书》, 讲述 Rust unsafe 编程的暗黑艺术. |
Unsafe Code Guidelines 🚧 | 编写 unsafe 代码的简要指南. |
Unstable Book | 关于 unstable 的信息, 如 #![feature(...)] . |
The Cargo Book | 如何使用 cargo 以及修改 Cargo.toml . |
The CLI Book | 如何创建 CLI 工具. |
The Embedded Book | 在嵌入式和 #![no_std] 下工作. |
The Embedonomicon | 运行在 Cortex-M 的首个 #![no_std] 指南. |
The WebAssembly Book | 和 Web 以及 .wasm 打交道. |
The wasm-bindgen Guide | 如何生产 Rust 和 JavaScript API 的绑定. |
其他非官方书籍参见 Little Book of Rust Books.
通用组件的综合查找表.
列表 📋 | 说明 |
---|---|
Rust Changelog | 查找某个特定版本的变更记录. |
Rust Forge | 列出了为编译器奋斗的组织和贡献者. |
Rust Platform Support | 所有支持的平台和优先级 (Tier). |
Rust Component History | 检查某个平台上 Rust 工具链在 nightly 能否正常工作. |
ALL the Clippy Lints | 列出了所有你可能感兴趣的 clippy lints. |
Configuring Rustfmt | 列出了所有你可以在 .rustfmt.toml 中设置的 rustfmt 选项. |
Compiler Error Index | 想要知道 E0404 什么意思吗? |
提供信息或工具的在线服务.
服务 ⚙️ | 说明 |
---|---|
crates.io | Rust 的所有第三方库. |
std.rs | 标准库 std 文档短链接. |
docs.rs | 第三方库文档, 都是自动从源码构建的. |
lib.rs | Rust 非官方的库和应用. |
caniuse.rs | 检查某个特性在哪个 Rust 版本引入或稳定. |
Rust Playground | 分享一些 Rust 代码片段. |
Rust Search Extension | 用于搜索文档, crate, 属性和书籍的浏览器插件. |
点击这里找到最新的 PDF 文件下载即可. 自己也可以直接通过文件 > 打印并选择“保存成 PDF”(Chrome 和 Edge 是可以的, Firefox 可能有点问题).