Rust的Unsafe Rust

我爱海鲸 2024-03-10 20:16:58 rust学习

简介不安全的Rust、extern

 

1、不安全Rust

隐藏着第二个语言,它没有强制内存安全保证:

Unsafe Rust(不安全的Rust)一和普通的Rust一样,但提供了额外的“超能力”

Unsafe Rust存在的原因:

-静态分析是保守的。

   使用Unsafe Rust:我知道自己在做什么,并承担相应风险

-计算机硬件本身就是不安全的,Rust需要能够进行底层系统编程

Unsafe超能力

使用unsafe 关键字来切换到unsafe Rust,开启一个块,里面放着unsafe 代码

Unsafe Rust里可执行的四个动作(unsafe超能力)

   解引用原始指针

   调用unsafe函数或方法

   访问或修改可变的静态变量

   实现unsafe trait

注意:
- unsafe并没有关闭借用检查或停用其它安全检查

任何内存安全相关的错误必须留在unsafe 块里

尽可能隔离unsafe 代码,最好将其封装在安全的抽象里,提供安全的API

解引用原始指针

原始指针
―可变的: *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代码块中进行使用:

fn main() {
  let mut num = 5;

  let r1 = &num as *const i32;
  let r2 = &mut num as *mut i32;

    unsafe {
        println!("r1:{}",*r1);
        println!("r2:{}",*r2);
    }


  let address = 0x0123456usize;

  let r = address as *const i32;

  unsafe {
    println!("r1:{}",*r);
  }

}

为什么要用原始指针?

与C语言进行接口

构建借用检查器无法理解的安全抽象

调用unsafe函数或方法

unsafe函数或方法:在定义前加上了unsafe关键字

   调用前需手动满足一些条件(主要靠看文档),因为Rust无法对这些条件进行验证

   需要在unsafe块里进行调用

unsafe fn dangerous() {
    
}

fn main() {
    unsafe {
        dangerous();
    }
}

创建unsafe代码的安全抽象

函数包含unsafe 代码并不意味着需要将整个函数标记为unsafe

将unsafe 代码包裹在安全函数中是一个常见的抽象

fn split_at_mut(slice: &mut [i32],mid:usize)->(&mut [i32],&mut [i32]) {
    let len = slice.len();

    assert!(mid <= len);

    (&mut slice[..mid],&mut slice[mid..])

}

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

}

上面这个例子中,我们对一个切片进行了分割,但是 (&mut slice[..mid],&mut slice[mid..])这一行代码会报错,因为rust无法对一个切片进行两次可变的借用。

下面我们使用unsafe代码进行改写:

use core::slice;

fn split_at_mut(slice: &mut [i32],mid:usize)->(&mut [i32],&mut [i32]) {
    let len = slice.len();
    let ptr = slice.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 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]);

}

使用extern函数调用外部代码

extern 关键字:简化创建和使用外部函数接口(FFI)的过程。

外部函数接口( FFl,Foreign Function Interface):它允许一种编程语言定义函数,并让其它编程语言能调用这些函数

extern "C" {
    fn abs(imput:i32) -> i32;
}

fn main() {
    unsafe {
        println!("Absolute value of -3 according to c:{}",abs(-3));
    }
}

应用二进制接口(ABl,Application Binary Interface):定义函数在汇编层的调用方式

“C”ABI是最常见的ABI,它遵循C语言的ABI

从其它语言调用Rust函数

可以使用extern 创建接口,其它语言通过它们可以调用Rust的函数

在fn 前添加extern关键字,并指定ABI

还需添加#[no_mangle]注解:避免Rust在编译时改变它的名称

#[no_mangle]
pub extern "C" fn call_from_c() {
    println!("Just called a Rust function from C!");
}
fn main() {}

访问或修改一个可变静态变量

Rust支持全局变量,但因为所有权机制可能产生某些问题,例如数据竞争

在Rust里,全局变量叫做静态( static)变量

static HELLO_WORLD:&str = "Hello,World";

fn main() {
    println!("name is {}",HELLO_WORLD);
}

静态变量

   静态变量与常量类似

   命名:SCREAMING_SNAKE_CASE

   必须标注类型

   静态变量只能存储‘static生命周期的引用,无需显式标注

   访问不可变静态变量是安全的

常量和不可变静态变量的区别

   静态变量:有固定的内存地址,使用它的值总会访问同样的数据

   常量:允许使用它们的时候对数据进行复制

   静态变量:可以是可变的,访问和修改静态可变变量是不安全(unsafe)的

static mut COUNT:u32 = 0;

fn add_to_count(inc:i32) {
    unsafe {
        COUNTER += inc;
    }
}

fn main() {
    add_to_count(3);

    unsafe {
        println!("COUNTER {}",COUNT);
    }
}

实现不安全(unsafe) trait

当某个trait中存在至少一个方法拥有编译器无法校验的不安全因素时,就称这个trait是不安全的

声明unsafe trait:在定义前加unsafe 关键字

   该trait只能在unsafe 代码块中实现

unsafe trait Foo {
    
}

unsafe impl Foo for i32 {
    
}

fn main() {
    
}

何时使用unsafe代码

编译器无法保证内存安全,保证unsafe 代码正确并不简单

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

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

 

你好:我的2025

上一篇:Rust的模式匹配

下一篇:Rust的高级Trait