跳过正文

19. 不安全:unsafe

·
Rust
目录
rust-lang - 这篇文章属于一个选集。
§ 19: 本文

使用 unsafe 关键字将 Rust 安全代码和非安全代码建立边界。

unsafe {} 用于指示编译器忽略一些严格的安全检查(但不忽略所有检查), 主要使用场景:

  1. 读写裸指针(在安全代码中可以创建、转移、比较 裸指针,但是不能使用裸指针,如解引用和赋值);
  2. 调用外部库函数(FFI 函数);
  3. 调用 unsafe 函数或方法;
  4. 读写 static mut 全局变量;
  5. 实现 unsafe trait;
  6. 读写 union 对象;

unsafe 代码(func/block/trait)都有使用前提,使用方需要保证满足这些约束,防止出现未定义的行为。

Rust 编译器、type checker、borrow checker 和其它静态 checker 会详细检查代码的合法性,但 unsafe 代码不会有这些安全性优势,需要开发者自己来保证。常见做法:将 unsafe block 封装在安全的函数中。

// from_utf8_unchecked 是 unsaft 函数,只能在 unsafe 上下文中调用
unsafe {
    String::from_utf8_unchecked(ascii)
}

// 调用外部库中的函数
extern "C" {
    fn abs(input: i32) -> i32;
}

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

// 读写 static mut 变量
static mut COUNTER: u32 = 0;

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

fn main() {
    add_to_count(3);
    unsafe {
        println!("COUNTER: {}", COUNTER);
    }
}

// 在 unsafe block 中读写裸指针:
fn main() {
    let mut a: usize = 0;
    let ptr = &mut a as *mut usize;
    unsafe {
        *ptr.offset(3) = 0x7ffff72f484c;
    }
}
// $ cargo build
//      Compiling unsafe-samples v0.1.0
//       Finished debug [unoptimized + debuginfo] target(s) in 0.44s
// $ ../../target/debug/crash
//     crash: Error: .netrc file is readable by others.
//     crash: Remove password or make file unreadable by others. Segmentation fault (core dumped)

裸指针的 unsafe 特性体现在:

  1. 允许忽略借用规则:可以同时拥有同一个内存地址的可变和不可变指针,或者拥有指向同一个地址的多个可变指针;
  2. 不能保证总是指向有效的内存地址;
  3. 允许为空(null);
fn main() {
    let p;
    {
        let x = 5;
        p = &x as *const i32; // 合法
    }
    unsafe {
        // 未定义行为:p 悬垂了,但编译器不报错
        println!("{}", *p); // 6

        // 未定义行为:向只读对象写内容
        let pm = p as *mut i32;
        // 编译器不检查,不报错
        *pm = 6;
        println!("{}", *pm); // 6
    }
}

unsafe function
#

是添加了 unsafe 标识的函数,它的 body 是 unsafe block:

  • unsafe 和普通函数完全一样,但用 unsafe 标记后表示调用该函数需要满足一些先决条件,需要开发者来保证。
  • 只能在 unsafe function/block 中调用 unsafe 函数。
pub unsafe fn from_bytes_unchecked(bytes: Vec<u8>) -> Ascii {
    Ascii(bytes)
}

// 使用 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();
    assert!(mid <= len);
    unsafe {
        // 需要在 unsafe block 中调用 unsafe 函数
        (slice::from_raw_parts_mut(ptr, mid),
       slice::from_raw_parts_mut(ptr.offset(mid as isize), len - mid))
    }
}

use std::slice;
let address = 0x01234usize;
let r = address as *mut i32;
let slice : &[i32] = unsafe {
    slice::from_raw_parts_mut(r, 10000)
};

unsafe trait
#

是添加了 unsafe 标识的 trait,在 impl 它时也必须添加 unsafe 标识:

  • unsafe trait 和普通 trait 完全一样,只是表明该 trait 的定义或实现有一些前提约束,Rust 编译器不会检查,需要开发者来保证。
// 定义 unsafe trait
pub unsafe trait Zeroable {}

// 实现 unsafe trait
unsafe impl Zeroable for u8 {}
unsafe impl Zeroable for i32 {}
unsafe impl Zeroable for usize {}

// 使用 unsafe trait 的函数或方法
use core::nonzero::Zeroable;

fn zeroed_vector<T>(len: usize) -> Vec<T> where T: Zeroable
{
    let mut vec = Vec::with_capacity(len);
    unsafe {
        std::ptr::write_bytes(vec.as_mut_ptr(), 0, len);
        vec.set_len(len);
    }
    vec
}
rust-lang - 这篇文章属于一个选集。
§ 19: 本文

相关文章

1. 标识符和注释:identify/comment
·
Rust
Rust 标识符介绍
10. 泛型和特性:generic/trait
·
Rust
Rust 泛型和特性
11. 类型协变:type coercion
·
Rust
Rust 高级话题:子类型和类型协变
12. 迭代器:iterator
·
Rust
Rust 迭代器