十七、Rust 进阶特性 (一)
1. 不安全的 Rust
-
之所以存在这样的特性,是因为静态分析本质上是保守的,且底层计算机硬件固然存在一定的不安全性
-
unsafe Rust 提供的能力
- 解引用裸指针
- 调用不安全的函数或方法
- 访问或修改可变静态变量
- 实现不安全 trait
- 访问
union的字段- 此时不会关闭借用检查器或禁用任何其他 Rust 安全检查
1.1 解引用裸指针
-
裸指针是可变或不可变的,分别写作
*const T和*mut T- 不可变:指针解引用之后不能直接赋值
-
裸指针的特点
- 允许忽略借用规则,可以同时拥有不可变和可变的指针,或多个指向相同位置的可变指针
- 不保证指向有效的内存
- 允许为空
- 不能实现任何自动清理功能
-
通过引用,创建指定类型的裸指针
fn main() { // 可以在安全代码中 创建 裸指针,但是不能在安全代码中解引用裸指针 // 创建指定类型的裸指针 let mut num = 5; let r1 = &num as *const i32; let r2 = &mut num as *mut i32; // 创建指向任意内存地址的裸指针 let address = 0x012345usize; let r3 = address as *const i32; unsafe { // 只能在 不安全块 中解引用裸指针 println!("r1 is: {}", *r1); println!("r2 is: {}", *r2); } } -
裸指针的用处
- 调用 C 代码接口
- 构建借用检查器无法理解的安全抽象
- ......
1.2 调用不安全的函数或方法
-
这些函数和方法与普通的函数和方法非常相似,但是是以
unsafe关键字开头的 -
不安全的函数或方法,只能在不安全块中调用
-
不安全函数体也是有效的
unsafe块 -
创建不安全代码的安全抽象
-
一个示例
// 来自官方教程的一个示例 use std::slice; fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { let len = slice.len(); let ptr = slice.as_mut_ptr(); // 如果传递进来的 index 没有数组长,就意味着参数错误,此时应当 panic assert!(mid <= len); // Rust 还没有智能到判定是不是借用数组的不同部分 // 因此如果返回结果没有封装到 unsafe 块中就会编译报错 unsafe { (slice::from_raw_parts_mut(ptr, mid), slice::from_raw_parts_mut(ptr.offset(mid as isize), len - mid)) }
-
} ```
-
调用外部代码
-
关键字:
extern -
作用
- 创建可被其他语言程序调用的函数接口
- 使用外部函数接口 (FFI)
-
使用外部函数接口
// 来自官方教程的一个示例 extern "C" { fn abs(input: i32) -> i32; } fn main() { unsafe { println!("Absolute value of -3 according to C: {}", abs(-3)); } } -
创建对其他语言提供的接口
// 来自官方教程的一个示例 #[no_mangle] // 这个注解是为了告诉 Rust 编译器不要 mangle 此函数的名称 pub extern "C" fn call_from_c() { println!("Just called a Rust function from C!"); }
-
1.3 访问或修改可变静态变量
-
全局变量,在 Rust 中被称为 静态 (static) 变量
- 静态变量的值有一个固定的内存地址
-
静态变量只能储存拥有
'static生命周期的引用- 访问不可变静态变量是安全的
-
常量
- 允许在任何被使用到的时候被复制
-
常量与静态变量的区别
- 可复制性
- 静态变量不可被复制,常量可以
- 可改变性
- 静态变量是可变的,常量一定是不可变的
- 可复制性
-
注意:访问和修改可变静态变量都是不安全的 (注意,访问也是不安全的,不仅仅修改是不安全的)
// 来自官方教程的一个示例 static mut COUNTER: u32 = 0; // 可变的静态变量 fn add_to_count(inc: u32) { unsafe { // 修改可变静态变量是 **不安全的** COUNTER += inc; } } fn main() { add_to_count(3); unsafe { // 访问可变静态变量也是 **不安全的** println!("COUNTER: {}", COUNTER); } } -
实现不安全 trait
- 当 trait 中至少有一个方法包含编译器不能验证的不变量时,这个 trait 就是不安全的
2. 高级 trait
2.1 关联类型
- 一种将类型占位符与 trait 相关联的方式,这样 trait 的方法签名中就可以使用这些占位符类型
- 看起来像一个类似泛型的概念
- 与泛型的对比
- 如果采用泛型实现,则可以为一个类型实现 trait 的多种泛型
- 如果使用关联类型实现,则只能为一个类型实现 trait 的一种形式,这也是关联类型的适用场景
2.2 默认泛型类型参数
-
类似于
Typscript里的默认泛型类型// Typescript 里的默认泛型类型 interface Counter<T = number> { count: T; } -
Rust 中的默认泛型类型
struct Counter<T = i32> { count: T, } -
示例:运算符重载
-
只能重载
std::ops中所列出的运算符和相应的 trait -
Addtrait 的定义trait Add<RHS=Self> { type Output; fn add(self, rhs: RHS) -> Self::Output; }
-
-
作用
- 扩展类型而不破坏现有代码
- 支持在特定情况进行自定义,虽然这种自定义在大部分情况下都是不需要的
2.3 同名方法的歧义消除
-
多 trait 和类型本身有同名方法
// 来自官方教程的一个示例 trait Pilot { fn fly(&self); } trait Wizard { fn fly(&self); } struct Human; impl Pilot for Human { fn fly(&self) { println!("This is your captain speaking."); } } impl Wizard for Human { fn fly(&self) { println!("Up!"); } } impl Human { fn fly(&self) { println!("*waving arms furiously*"); } } fn main() { let person = Human; Pilot::fly(&person); // 调用实现的 Pilot trait 上的 fly 方法 Wizard::fly(&person); // 调用实现的 Wizard trait 上的 fly 方法 person.fly(); // 调用直接实现在 Human 结构体上的 fly 方法 } -
trait 与类型的同名关联函数调用
- 需要用到一个新语法:完全限定语法
- 语法格式:
<Type as Trait>::function(receiver_if_method, next_arg, ...);
- 语法格式:
// 来自官方教程的一个示例 trait Animal { // trait 的关联函数 fn baby_name() -> String; } struct Dog; impl Dog { // Dog 结构体的关联函数 fn baby_name() -> String { String::from("Spot") } } impl Animal for Dog { fn baby_name() -> String { String::from("puppy") } } fn main() { // 此时调用的是结构体上的关联函数 println!("A baby dog is called a {}", Dog::baby_name()); // 此时才是调用的 trait 上的关联函数 println!("A baby dog is called a {}", <Dog as Animal>::baby_name()); } - 需要用到一个新语法:完全限定语法
2.4 trait 间依赖
-
trait 之间相互依赖,一个 trait 使用另一个 trait 的功能
-
此时,实现 trait A 时,也必须实现 trait B
-
示例代码
// 来自官方教程的一个示例 use std::fmt; // 如果要实现 OutlinePrint trait,就必须同时实现 fmt::Display trait trait OutlinePrint: fmt::Display { // 注意这个语法即可 fn outline_print(&self) { let output = self.to_string(); let len = output.len(); println!("{}", "*".repeat(len + 4)); println!("*{}*", " ".repeat(len + 2)); println!("* {} *", output); println!("*{}*", " ".repeat(len + 2)); println!("{}", "*".repeat(len + 4)); } } struct Point { x: i32, y: i32, } // 如果只实现了 OutlinePrint trait,则编译会失败 impl OutlinePrint for Point {} // 必须同时实现了 fmt::Display trait,才会编译成功 impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "({}, {})", self.x, self.y) } }
2.5 newtype 模式
-
orphan rule: 只有当 trait 或者要实现 trait 的类型位于 crate 的本地作用域时,才能为该类型实现 trait -
绕开上述的作用域限制,示例代码
use std::fmt; // 增加一个 wrapper 类型,让这个类型可以实现 Display::fmt 方法 struct Wrapper(Vec<String>); impl fmt::Display for Wrapper { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[{}]", self.0.join(", ")) } } fn main() { let w = Wrapper(vec![String::from("hello"), String::from("world")]); println!("w = {}", w); }