Rust 安全代码
安全与非安全代码对比 安全与非安全代码交互 非安全Rust能做什么 编写非安全代码数据布局
repr(Rust) 类型中的奇行种 可选的数据表达方式所有权
所有权 引用 别名 生命周期 生命周期的局限 省略生命周期 无界生命周期 高阶trait边界 子类型和变性 Drop检查 幽灵数据 分解借用类型转换
类型转换 强制类型转换 点操作符 显式类型转换 变形未初始化内存
未初始化内存 安全方式 Drop标志 非安全方式资源管理
基于所有权的资源管理 构造函数 析构函数 泄露展开
展开 异常安全性 污染并发
并发 竞争 Send和Sync 原子操作实现 Vec
实现 Vec 布局 内存分配 push和pop 回收资源 DeRef 插入和删除 IntoIter RawVec Drain 处理零尺寸类型 最终代码FFI
FFIRust 安全代码
安全与非安全代码对比 安全与非安全代码交互 非安全Rust能做什么 编写非安全代码数据布局
repr(Rust) 类型中的奇行种 可选的数据表达方式所有权
所有权 引用 别名 生命周期 生命周期的局限 省略生命周期 无界生命周期 高阶trait边界 子类型和变性 Drop检查 幽灵数据 分解借用类型转换
类型转换 强制类型转换 点操作符 显式类型转换 变形未初始化内存
未初始化内存 安全方式 Drop标志 非安全方式资源管理
基于所有权的资源管理 构造函数 析构函数 泄露展开
展开 异常安全性 污染并发
并发 竞争 Send和Sync 原子操作实现 Vec
实现 Vec 布局 内存分配 push和pop 回收资源 DeRef 插入和删除 IntoIter RawVec Drain 处理零尺寸类型 最终代码FFI
FFIRust Send和Sync
不是所有类型都遵守可变性的原则。有一些类型允许你拥有一块内存的多个别名,同时还改变内存的值。除非这些类型使用同步来控制访问,否则它们就不是线程安全的。Rust根据Send和Sync这两个trait获取相关信息。
- 如果一个类型可以安全地传递给另一个线程,这个类型是
Send - 如果一个类型可以安全地被多个线程共享(也就是
&T是Send),这个类型是Sync
Send和Sync是Rust并发机制的基础。因此,Rust赋予它们许多的特性,以保证它们能正确工作。首当其冲的,它们都是非安全trait。这表明它们的实现也是非安全的,而其他的非安全代码则可以假设这些实现是正确的。由于它们是标志trait(它们没有任何关联的方法),“正确地实现”仅仅意味着实现满足它所需要的内部特征。不正确地实现Send和Sync会导致未定义行为。
Send和Sync还是自动推导的trait。和其他的trait不同,如果一个类型完全由Send或Sync组成,那么这个类型本身也是Send或Sync。几乎所有的基本类型都是Send和Sync,因此你能见到的很多类型也就都是Send和Sync。
主要的例外情况有:
- 裸指针不是
Send也不是Sync(因为它们没有安全性保证) UnsafeCell不是Sync(所以Cell和RefCell也不是)Rc不是Send或Sync(因为引用计数是共享且非同步的)
Rc和UnsafeCell是典型的非线程安全的:它们允许非同步地共享可变状态。可是,裸指针严格来说并不一定非得是非线程安全不可。通过裸指针做任何有意义的事情都需要先对它解引用,这一步就已经是非安全的了。从这个角度来说,有人可能会认为把它标为线程安全的也未尝不可。
可是,它们被标为非线程安全的主要目的是避免包含它们的类型自动成为线程安全的。这些类型都有着重要的不可追踪的所有权,保证它们线程安全需要花费大量的精力,而他们的作者不太可能做到这一点。Rc就是一个很好的例子,一个包含*mut的类型绝对不能是线程安全的。
不是自动推导的类型也可以很容易地实现Send和Sync:
struct MyBox(*mut u8);
unsafe impl Send for MyBox {}
unsafe impl Sync for MyBox {}
还有一个很少见的场景,一个类型被自动推导为Send或Sync,但是它其实不满足二者的要求。这是我们可以去掉Send和Sync的实现:
#![feature(option_builtin_traits)]
// 我对于同步的基础类型有着神奇的语义
struct SpecialThreadToken(u8);
impl !send for SpecialThreadToken {}
impl !Sync for SpecialThreadToken {}
注意,一个类型自己不可能被不正确地推导为Send和Sync。只有当类型和其他的非安全代码一起实现了一些特殊行为时,它才可能成为一个不正确的Send或Sync。
大部分使用裸指针的类型都应该把裸指针用一种抽象隐藏起来,以保证类型可以被推导为Send和Sync。比如,所有Rust的标准集合类型都是Send和Sync(在他们包含Send和Sync类型的情况下),虽然它们都大量使用了裸指针处理内存分配和复杂的所有权。类似的,大部分这些集合的迭代器也是Send和Sync,因为它们的行为很像这些集合的&或者&mut。
下一章:Rust 原子操作
Rust几乎全部抄袭了C11关于原子操作的内存模型。这么做并不是因为这个模型多么的优秀或者易于理解。事实上,这个模型非常的复杂,而且有一些已知的缺陷。不过,所有的原子操作模型其实都不怎么样,我们不得不因此做出一些妥协。至 ...
AI 中文社