rust(十九)- 不安全Rust

203 阅读4分钟

匹配命名变量

  • 隐藏着第二个语言,它没有强制内存安全保证:Unsafe Rust(不安全的Rust)
    • 和普通的Rust一样,但提供了额外的“超能力”
  • unsafe Rust 存在的原因
    • 静态分析是保守的
      • 使用unsafe rust:我知道自己在做什么,并承担相应风险
    • 计算机硬件本身就是不安全的,Rust需要能够进行底层系统编程
  • unsafe超能力
    • 使用unsafe关键字来切换到unsafe rust,开启一个块,里面放着unsafe代码
    • unsafe rust里可执行的四个动作(Unsafe超能力)
      • 解引用原始指针
        • 原始指针
          • 可变的:*mut T
          • 不可变的: *const T .意味着指针在解引入后不能直接对其进行赋值
          • 注意: 这里的*不是解引用符号,它是类型名的一部分
        • 与引用不同,原始指针
          • 允许通过同时具有不可变和可变指针或多个指向同一位置的可变指针来忽略借用规则
          • 无法保证能指向合理的内存
          • 允许为null
          • 不实现任何自动清理
        • 放弃保证的安全,换取更好的性能/与其它语言或硬件接口的能力
        fn main() {
          let mut num = 5;
        
          let r1 = &num as *const i32;
          let r2 = &mut num as *mut i32;
        
          unsafe {
              println!("r1 is: {}", *r1);
              println!("r2 is: {}", *r2);
          }
        }
        
      • 调用unsafe函数或方法
      • 访问或修改可变的静态变量
      • 实现unsafe trait
    • 注意:
      • unsafe并没有关闭借用检查或停用其它安全检查
      • 任何内存安全相关的错误必须留在unsafe块里
      • 尽可能隔离unsafe代码,最好将其封装在安全的抽象里,提供安全的api
  • 调用unsafe函数或方法
    • unsafe函数或方法:在定义前加上了unsafe关键字
      • 调用前需要手动满足一些条件(主要看文档),因为rust无法对这些条件进行验证
      • 需要在unsafe块里进行调用
      fn main() {
      unsafe fn dangerous() {}
          unsafe {
              dangerous();
          }
      }
      
    • 创建unsafe代码的安全抽象
      • 函数饮食unsafe代码并不意味着需要要将整个函数票房为unsafe
      • 将unsafe代码包裹在安全函数中是一个常见的抽象
      fn main() {
          let mut v = vec![1, 2, 3, 4, 5, 6];
      
          let r = &mut v[..];
      
          let (a, b) = r.split_at_mut(3);
      
          assert_eq!(a, &mut [1, 2, 3]);
          assert_eq!(b, &mut [4, 5, 6]);
      }
      use std::slice;
      
      fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
          let len = values.len();
          let ptr = values.as_mut_ptr();
      
          assert!(mid <= len);
      
          unsafe {
              (
                  slice::from_raw_parts_mut(ptr, mid),
                  slice::from_raw_parts_mut(ptr.add(mid), len - mid),
              )
          }
      }
      
      fn main() {
          let mut vector = vec![1, 2, 3, 4, 5, 6];
          let (left, right) = split_at_mut(&mut vector, 3);
      }
      
  • 使用extern函数调用外部代码
    • extern 关键字:简化创建和使用外部函数接口(FFI)的过程
    • 外部函数接口(FFI, foreign function interface):它允许一种编程语言定义函数,并让其它编程语言能调用这些函数。
    extern "C" {
          fn abs(input: i32) -> i32;
      }
    
      fn main() {
          unsafe {
              println!("Absolute value of -3 according to C: {}", abs(-3));
          }
       }
    
    • 应用二进制接口(ABI,application binary interface):定义函数在汇编层的调用方式
    • “C”ABI最常见的ABI,它遵循C语言的ABI
  • 从其它语言调用Rust函数
    • 可以使用extern创建接口, 其它语言通过它们可以调用Rust的函数
    • 在FN前添加extern关键字,并指定ABI
    • 还需添加#[no_mangle]注解:避免rust在编译时改变它的名称
      #![allow(unused)]
      fn main() {
      #[no_mangle]
      pub extern "C" fn call_from_c() {
          println!("Just called a Rust function from C!");
      }
      }
    
  • 访问或修改一个可变静态变量
    • Rust 支持全局变量,但因为所有的权机制可能产生某些问题,例如数据竞争
    • 在Rust里,全局变量叫做静态(static)变量
    static HELLO_WORLD: &str = "Hello, world!";
    
      fn main() {
          println!("name is: {}", HELLO_WORLD);
      }
    
  • 静态变量
    • 静态变量和常量类似
    • 命名:screaming_snake_case
    • 必须标注类型
    • 静态变量只能存储‘static生命周期的引用,无需显式标注
    • 访问不可变静态变量是安全的
  • 常量和不可变静态变量的区别
    • 静态变量:有固定的内存地址,使用它的值总会访问同样的数据
    • 常量:允许使用它们的时候对数据进行复制
    • 静态变量:可以是可变的,访问和修改静态可变量是不安全(unsafe)的
  • 实现不安全(unsafe)trait
    • 当某个trait中存在至少一个方法拥有编译器无法校验的不安全因素时,就称这个tait是不安全的
    • 声明unsafe trait:在定义前加unsafe关键字
      • 该tait只能在unsafe代码块中实现
      unsafe trait Foo {
          // methods go here
      }
      
      unsafe impl Foo for i32 {
          // method implementations go here
      }
      
      fn main() {}
      
      
    • 何时使用unsafe代码
      • 编译器无法保证内存安全,保证unsafe代码正确并不简单

      • 有充足理由使用unsafe代码时,就可以这样做

      • 通过显式标记unsafe,可以在出现问题时轻松的定们