当前位置:   article > 正文

Rust入门(十四):不安全Rust_rust unsafe

rust unsafe

不安全Rust

Rust 可以不强制执行内存安全保证,这被称为 不安全 Rustunsafe Rust),这类代码会提供额外的超能力。

可以通过 unsafe 关键字来切换到不安全 Rust,接着可以开启一个新的存放不安全代码的块,有五类可以在不安全 Rust 中进行而不能用于安全 Rust 的操作:

  • 解引用裸指针
  • 调用不安全的函数或方法
  • 访问或修改可变静态变量
  • 实现不安全 trait
  • 访问 union 的字段

解引用裸指针

不安全 Rust 有两个被称为 裸指针raw pointers)的类似于引用的新类型。和引用一样,裸指针是不可变或可变的,分别写作 *const T*mut T。这里的星号不是解引用运算符;它是类型名称的一部分。在裸指针的上下文中,不可变 意味着指针解引用之后不能直接赋值。

裸指针与引用和智能指针的区别在于

  • 允许忽略借用规则,可以同时拥有不可变和可变的指针,或多个指向相同位置的可变指针
  • 不保证指向有效的内存
  • 允许为空
  • 不能实现任何自动清理功能
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
  • 1
  • 2
  • 3

可以在安全代码中 创建 裸指针,只是不能在不安全块之外 解引用 裸指针,稍后便会看到。使用 as 可以将不可变和可变引用强转为对应的裸指针类型

let address = 0x012345usize;
let r = address as *const i32;
  • 1
  • 2

现在我们要做的就是对裸指针使用解引用运算符 *,这需要一个 unsafe 块,创建一个指针不会造成任何危险;只有当访问其指向的值时才有可能遇到无效的值:

let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
unsafe {
    println!("r1 is: {}", *r1);
    println!("r2 is: {}", *r2);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

调用不安全方法

不安全函数和方法与常规函数方法十分类似,除了其开头有一个额外的 unsafe

unsafe fn dangerous() {}
unsafe {
    dangerous();
}
  • 1
  • 2
  • 3
  • 4

仅仅因为函数包含不安全代码并不意味着整个函数都需要标记为不安全的。例如:标准库中的函数,split_at_mut,它获取一个 slice 并从给定的索引参数开始将其分为两个 slice,slice::from_raw_parts_mut 函数是不安全的因为它获取一个裸指针,并必须确信这个指针是有效的,所以需要包含在 unsafe 块中,但是无需将 split_at_mut 函数的结果标记为 unsafe,并可以在安全 Rust 中调用此函数。

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

    assert!(mid <= len);

    unsafe {
        (
            slice::from_raw_parts_mut(ptr, mid),
            slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

extern

有时你的 Rust 代码可能需要与其他语言编写的代码交互。为此 Rust 有一个关键字,extern,有助于创建和使用 外部函数接口Foreign Function Interface, FFI)。extern 块中声明的函数在 Rust 代码中总是不安全的。因为其他语言不会强制执行 Rust 的规则且 Rust 无法检查它们。如下的例子是:集成 C 标准库中的 abs 函数

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

fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

也可以使用 extern 来创建一个允许其他语言调用 Rust 函数的接口。不同于 extern 块,就在 fn 关键字之前增加 extern 关键字并指定所用到的 ABI。还需增加 #[no_mangle] 注解来告诉 Rust 编译器不要 mangle 此函数的名称。一旦其编译为动态库并从 C 语言中链接,call_from_c 函数就能够在 C 代码中访问:

#[no_mangle]
pub extern "C" fn call_from_c() {
    println!("Just called a Rust function from C!");
}
  • 1
  • 2
  • 3
  • 4

访问修改静态变量

全局变量在 Rust 中被称为 静态static)变量,静态变量中的值有一个固定的内存地址。使用这个值总是会访问相同的地址。另一方面,常量则允许在任何被用到的时候复制其数据

static HELLO_WORLD: &str = "Hello, world!";

fn main() {
    println!("name is: {}", HELLO_WORLD);
}
  • 1
  • 2
  • 3
  • 4
  • 5

访问和修改可变静态变量都是不安全的,如下:我们使用 mut 关键来指定可变性。任何读写 COUNTER 的代码都必须位于 unsafe 块中。

static mut COUNTER: u32 = 0;

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

fn main() {
    add_to_count(3);

    unsafe {
        println!("COUNTER: {}", COUNTER);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

不安全trait

当 trait 中至少有一个方法中包含编译器无法验证的不变式(invariant)时 trait 是不安全的。可以在 trait 之前增加 unsafe 关键字将 trait 声明为 unsafe,同时 trait 的实现也必须标记为 unsafe,例如:如果实现了一个包含一些不是 SendSync 的类型,比如裸指针,并希望将此类型标记为 SendSync,则必须使用 unsafe

unsafe trait Foo {
    // methods go here
}

unsafe impl Foo for i32 {
    // method implementations go here
}

fn main() {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

访问联合体

unionstruct 类似,但是在一个实例中同时只能使用一个声明的字段,也就是说,一个生命了多个字段的联合体,在使用的时候只能成为其中的一个字段类型,可以查看 参考文档 了解有关联合体的更多信息。

联合体主要用于和 C 代码中的联合体交互。访问联合体的字段是不安全的,因为 Rust 无法保证当前存储在联合体实例中数据的类型。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/酷酷是懒虫/article/detail/909613
推荐阅读
相关标签
  

闽ICP备14008679号