0%

2024秋操作系统训练营第一阶段总结——707state

Rustling总结

我想成为Rustacean,通过rustling的练习,我学到一些没接触过的Rust知识

repr attribute

在c/c++中,常常使用attribute((align))的方式来确保内存对齐,repr在rust的功能久石让每一个数据能够按照k的整数倍分配,k通常是基本类型。

Rusty 阶乘

1
2
3
pub fn factorial(num: u64) -> u64 {
(1..=num).fold(1, |acc, x| acc * x)
}

范围 (1..=num):

这个表达式创建了一个从 1 到 num 的范围,包括 num 本身。..= 是一个闭区间语法,表示这个范围是包含起始值和结束值的。

fold 方法:

fold 是一个高阶函数,它用于将一个迭代器中的所有元素合并成一个单一的值。
它的第一个参数是初始值,在这里是 1。这个值会作为累加器的初始状态。
第二个参数是一个闭包(匿名函数),它接受两个参数:acc(累加器的当前值)和 x(迭代器中的当前值)。
在每一次迭代中,闭包会将 acc 和 x 相乘,并返回新的累加器值。

流敏感分析

依靠 Borrow Checker 确保内存安全,原理如下:

执行路径分析:Rust 编译器在编译过程中会为每个变量跟踪其借用状态或所有权。在程序的不同控制流(如条件判断、循环、函数调用等)中,编译器会检查在这些不同路径下变量的状态变化,并在所有路径中保持一致性。

数据流分析:Rust 编译器通过数据流分析来确定变量的生存期、是否被借用、以及是否存在竞争条件。这个分析是流敏感的,意味着它会根据程序的控制流更新变量状态。

作用域检查:编译器在分析时会检查变量是否在作用域内、是否已经被销毁或者转移所有权。通过流敏感分析,编译器能够精确确定哪些变量在某条执行路径中被有效引用或借用。

take

在 Rust 中,take() 方法通常用于在某些容器类型(如 Option、Result 等)中“取走”值,将原来的值替换为一个默认值(通常是 None 或 Err),同时返回原来的值。

take() 经常用于以下场景:

转移所有权:从一个可选值中取出所有权,并清空该值,避免复制或克隆。

链表或树结构:当你遍历或修改链表或树结构时,可以用 take() 来“取走”节点的引用并避免借用冲突。

zero-cost futures

async/await实现的future类型不会引入任何额外的运行时开销。

无运行时依赖:与其他编程语言(如 JavaScript 或 Python)不同,Rust 的 async/await 本身不依赖特定的运行时机制。Future 是惰性的,它本质上是一个状态机,只有在被轮询时才会前进。虽然需要某种形式的运行时(如 Tokio 或 async-std)来调度异步任务,但这些运行时并没有与 async/await 特性本身紧耦合。

Any Trait

Rust中的Any trait允许在运行时进行类型检查和类型转换。这个类型在处理动态类型时较为有用。

功能:

  1. 类型检查:通过is::()方法,可以检查一个值是否是某种特定类型。

  2. 类型转换:使用downcast_ref::()和downcast::()方法,可以将一个&dyn Any或者Box类型的值转换回具体类型T。

注意:Any trait只能用于’static 生命周期的类型,这意味着他不能用于包含非静态引用的类型。

并且,Any会引入运行时开销,因为它依赖动态分派。

UnsafeCell

Rust 中内部可变性的核心原语。

如果您使用的是 &T,则通常在 Rust 中,编译器基于 &T 指向不可变数据的知识来执行优化。例如通过别名或通过将 &T 转换为 &mut T 来可变的该数据,被认为是未定义的行为。 UnsafeCell 选择退出 &T 的不可变性保证:共享的引用 &UnsafeCell 可能指向正在发生可变的数据。这称为内部可变性。

所有其他允许内部可变性的类型,例如 Cell 和 RefCell,在内部使用 UnsafeCell 来包装它们的数据。

所有其他允许内部可变性的类型,例如 Cell 和 RefCell,在内部使用 UnsafeCell 来包装它们的数据。

UnsafeCell API 本身在技术上非常简单: .get() 为其内容提供了裸指针 *mut T。正确使用该裸指针取决于您。

如果您使用生命周期 ‘a (&T 或 &mut T 引用) 创建安全引用,那么您不得以任何与 ‘a 其余部分的引用相矛盾的方式访问数据。 例如,这意味着如果您从 UnsafeCell 中取出 *mut T 并将其转换为 &T,则 T 中的数据必须保持不可变 (当然,对 T 中找到的任何 UnsafeCell 数据取模),直到引用的生命周期到期为止。 同样,如果您创建的 &mut T 引用已发布为安全代码,则在引用终止之前,您不得访问 UnsafeCell 中的数据。

对于没有 UnsafeCell<> 的 &T 和 &mut T,在引用过期之前,您也不得释放数据。作为一个特殊的例外,给定一个 &T,它在 UnsafeCell<> 内的任何部分都可能在引用的生命周期期间被释放,在最后一次使用引用之后 (解引用或重新借用)。 因为您不能释放引用指向的部分,这意味着只有当它的每一部分 (包括填充) 都在 UnsafeCell 中时,&T 指向的内存才能被释放。

但是,无论何时构造或解引用 &UnsafeCell,它仍必须指向活动内存,并且如果编译器可以证明该内存尚未被释放,则允许编译器插入虚假读取。

在任何时候,您都必须避免数据竞争。如果多个线程可以访问同一个 UnsafeCell,那么任何写操作都必须在与所有其他访问 (或使用原子) 相关之前发生正确的事件。

为了帮助进行正确的设计,以下情况明确声明为单线程代码合法:

&T 引用可以释放为安全代码,并且可以与其他 &T 引用共存,但不能与 &mut T 共存

&mut T 引用可以发布为安全代码,前提是其他 &mut T 和 &T 都不共存。&mut T 必须始终是唯一的。

请注意,虽然可以更改 &UnsafeCell 的内容 (即使其他 &UnsafeCell 引用了该 cell 的别名) 也可以 (只要以其他方式实现上述不变量即可),但是具有多个 &mut UnsafeCell 别名仍然是未定义的行为。 也就是说,UnsafeCell 是一个包装器,旨在通过 &UnsafeCell<> 与 shared accesses (i.e. 进行特殊交互 (引用) ; 通过 &mut UnsafeCell<> 处理 exclusive accesses (e.g. 时没有任何魔术) : 在该 &mut 借用期间, cell 和包装值都不能被别名。

.get_mut() 访问器展示了这一点,该访问器是产生 &mut T 的 safe getter。

UnsafeCell 与其内部类型 T 具有相同的内存表示。此保证的结果是可以在 T 和 UnsafeCell 之间进行转换。 将 Outer 类型内的嵌套 T 转换为 Outer<UnsafeCell> 类型时必须特别小心: 当 Outer 类型启用 niche 优化时,这不是正确的。

std::mem::MaybeUninit

Rust 编译器要求变量要根据其类型正确初始化。

比如引用类型的变量必须对齐且非空。这是一个必须始终坚持的不变量,即使在 Unsafe 代码中也是如此。因此,零初始化引用类型的变量会导致立即未定义行为,无论该引用是否访问过内存。

编译器利用这一点,进行各种优化,并且可以省略运行时检查。

由调用者来保证MaybeUninit确实处于初始化状态。当内存尚未完全初始化时调用 assume_init() 会导致立即未定义的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

#![allow(unused)]

fn main() {
use std::mem::{self, MaybeUninit};
// 不符合:零初始化引用
let x: &i32 = unsafe { mem::zeroed() }; // undefined behavior! ⚠️
// 等价于 `MaybeUninit<&i32>`:
let x: &i32 = unsafe { MaybeUninit::zeroed().assume_init() }; // undefined behavior!
// 不符合:布尔值必须初始化
let b: bool = unsafe { mem::uninitialized() }; // undefined behavior! ⚠️
// 等价于 `MaybeUninit<bool>`:
let b: bool = unsafe { MaybeUninit::uninit().assume_init() }; // undefined behavior!
// 不符合:整数类型也必须初始化
let x: i32 = unsafe { mem::uninitialized() }; // undefined behavior! ⚠️
// 等价于 `MaybeUninit<i32>`:
let x: i32 = unsafe { MaybeUninit::uninit().assume_init() };

// 不符合:Vec未初始化内存使用 set_len 是未定义行为
let mut vec: Vec<u8> = Vec::with_capacity(1000);
unsafe { vec.set_len(1000); }
reader.read(&mut vec); // undefined behavior!
}

主要就是这些收获。