十七、Rust 进阶特性 (一)

207 阅读6分钟

十七、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

    • Add trait 的定义

      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);
    }