目录

第十七章 Unsafe Rust

Rust的安全保证是其最大的卖点,但有时这些保证会阻止你完成某些任务。Unsafe Rust允许你执行一些编译器无法保证内存安全的操作。本章将详细介绍何时以及如何使用Unsafe Rust。

1. 为什么要使用Unsafe

1.1 Safe Rust的限制

Rust编译器强制执行许多安全规则:

但在某些情况下,这些限制过于严格:

1.2 Unsafe Rust的能力

Unsafe Rust允许你执行五种操作:

重要:unsafe并不意味着代码一定危险或内存不安全。它只是将保证安全的责任从编译器转移到了程序员。

2. 原始指针(Raw Pointers)

2.1 原始指针类型

Rust有两种原始指针:

fn main() {
    let mut num = 5;
 
    // 创建原始指针
    let r1 = &num as *const i32;    // 不可变原始指针
    let r2 = &mut num as *mut i32;  // 可变原始指针
 
    // 注意:这里在safe代码中创建了原始指针是允许的
    // 但解引用它们需要unsafe块
}

2.2 解引用原始指针

fn main() {
    let mut num = 5;
    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;
 
    unsafe {
        println!("r1 is: {}", *r1);  // 5
        println!("r2 is: {}", *r2);  // 5
 
        *r2 = 10;
        println!("修改后 r1 is: {}", *r1);  // 10
    }
}

2.3 原始指针与引用的区别

特性 原始指针 引用
允许空值
允许多个可变指针
不保证指向有效内存
自动释放
实现Send/Sync 有条件

2.4 原始指针的使用场景

// 与C代码交互
extern "C" {
    fn abs(input: i32) -> i32;
}
 
fn main() {
    unsafe {
        println!("C abs(-3) = {}", abs(-3));
    }
}
// 实现自己的智能指针
struct MyBox<T> {
    ptr: *mut T,
}
 
impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        let ptr = Box::into_raw(Box::new(x));
        MyBox { ptr }
    }
 
    fn get(&self) -> &T {
        unsafe { &*self.ptr }
    }
 
    fn get_mut(&mut self) -> &mut T {
        unsafe { &mut *self.ptr }
    }
}
 
impl<T> Drop for MyBox<T> {
    fn drop(&mut self) {
        unsafe {
            Box::from_raw(self.ptr);  // 重新获取所有权并释放
        }
    }
}
 
fn main() {
    let mut b = MyBox::new(5);
    println!("{}", b.get());
    *b.get_mut() = 10;
    println!("{}", b.get());
}

3. Unsafe函数和方法

3.1 声明unsafe函数

unsafe fn dangerous() {
    println!("这是一个unsafe函数");
}
 
fn main() {
    unsafe {
        dangerous();  // 必须在unsafe块中调用
    }
}

3.2 创建safe的抽象

最佳实践是将unsafe代码封装在safe的API中:

use std::slice;
 
// 对外暴露safe接口
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = slice.len();
    assert!(mid <= len);
 
    // 内部使用unsafe
    unsafe {
        (
            slice::from_raw_parts_mut(slice.as_mut_ptr(), mid),
            slice::from_raw_parts_mut(slice.as_mut_ptr().add(mid), len - mid),
        )
    }
}
 
fn main() {
    let mut v = vec![1, 2, 3, 4, 5];
    let (left, right) = split_at_mut(&mut v, 2);
    println!("left: {:?}, right: {:?}", left, right);
}

3.3 使用extern函数

调用C代码

// 声明C函数
#[link(name = "c")]
extern "C" {
    fn printf(format: *const u8, ...) -> i32;
    fn strlen(s: *const u8) -> usize;
}
 
fn main() {
    unsafe {
        let msg = b"Hello, C!\0";
        printf(msg.as_ptr() as *const u8);
 
        let s = b"Rust\0";
        let len = strlen(s.as_ptr());
        println!("长度: {}", len);
    }
}

导出Rust函数给C

#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}
 
// 使用panic时防止未定义行为
#[no_mangle]
pub extern "C" fn safe_add(a: i32, b: i32) -> i32 {
    std::panic::catch_unwind(|| {
        a + b
    }).unwrap_or(0)
}

4. 可变静态变量

4.1 全局可变状态

static mut COUNTER: u32 = 0;
 
fn add_to_count(inc: u32) {
    unsafe {
        COUNTER += inc;
    }
}
 
fn main() {
    add_to_count(3);
 
    unsafe {
        println!("COUNTER: {}", COUNTER);
    }
}

警告:可变静态变量不是线程安全的,多线程访问会导致数据竞争。

4.2 使用同步原语替代

use std::sync::atomic::{AtomicU32, Ordering};
 
static COUNTER: AtomicU32 = AtomicU32::new(0);
 
fn add_to_count(inc: u32) {
    COUNTER.fetch_add(inc, Ordering::SeqCst);
}
 
fn main() {
    add_to_count(3);
    println!("COUNTER: {}", COUNTER.load(Ordering::SeqCst));
}

5. Unsafe Trait

5.1 声明unsafe trait

当trait的某些不变量编译器无法验证时,使用unsafe trait。

unsafe trait Foo {
    // 方法定义
}
 
unsafe impl Foo for i32 {
    // 实现
}

5.2 标准库中的unsafe trait

Send和Sync

// 实现Send表示类型可以安全地在线程间转移所有权
unsafe impl Send for MyType {}
 
// 实现Sync表示类型可以安全地被多个线程共享引用
unsafe impl Sync for MyType {}

GlobalAlloc

use std::alloc::{GlobalAlloc, Layout, System};
 
struct MyAllocator;
 
unsafe impl GlobalAlloc for MyAllocator {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        System.alloc(layout)
    }
 
    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        System.dealloc(ptr, layout)
    }
}
 
#[global_allocator]
static ALLOCATOR: MyAllocator = MyAllocator;

6. Union类型

Union类似于C的union,所有字段共享同一内存位置。

#[repr(C)]
union MyUnion {
    i: i32,
    f: f32,
}
 
fn main() {
    let mut u = MyUnion { i: 1 };
 
    unsafe {
        println!("作为i32: {}", u.i);
        u.f = 3.14;
        println!("作为f32: {}", u.f);
    }
}

7. 实现不安全的数据结构

7.1 实现自己的Vec

use std::alloc::{self, Layout};
use std::ptr::{self, NonNull};
 
struct MyVec<T> {
    ptr: NonNull<T>,
    len: usize,
    capacity: usize,
}
 
impl<T> MyVec<T> {
    fn new() -> Self {
        Self {
            ptr: NonNull::dangling(),
            len: 0,
            capacity: 0,
        }
    }
 
    fn push(&mut self, elem: T) {
        if self.len == self.capacity {
            self.grow();
        }
 
        unsafe {
            ptr::write(self.ptr.as_ptr().add(self.len), elem);
        }
        self.len += 1;
    }
 
    fn pop(&mut self) -> Option<T> {
        if self.len == 0 {
            return None;
        }
 
        self.len -= 1;
        unsafe {
            Some(ptr::read(self.ptr.as_ptr().add(self.len)))
        }
    }
 
    fn grow(&mut self) {
        let new_capacity = if self.capacity == 0 { 1 } else { self.capacity * 2 };
 
        let new_layout = Layout::array::<T>(new_capacity).unwrap();
 
        let new_ptr = if self.capacity == 0 {
            unsafe { alloc::alloc(new_layout) as *mut T }
        } else {
            let old_layout = Layout::array::<T>(self.capacity).unwrap();
            unsafe {
                alloc::realloc(
                    self.ptr.as_ptr() as *mut u8,
                    old_layout,
                    new_layout.size(),
                ) as *mut T
            }
        };
 
        self.ptr = NonNull::new(new_ptr).expect("分配失败");
        self.capacity = new_capacity;
    }
 
    fn len(&self) -> usize {
        self.len
    }
 
    fn capacity(&self) -> usize {
        self.capacity
    }
 
    fn get(&self, index: usize) -> Option<&T> {
        if index >= self.len {
            return None;
        }
        unsafe {
            Some(&*self.ptr.as_ptr().add(index))
        }
    }
}
 
impl<T> Drop for MyVec<T> {
    fn drop(&mut self) {
        if self.capacity == 0 {
            return;
        }
 
        // 逐个drop元素
        for i in 0..self.len {
            unsafe {
                ptr::drop_in_place(self.ptr.as_ptr().add(i));
            }
        }
 
        // 释放内存
        let layout = Layout::array::<T>(self.capacity).unwrap();
        unsafe {
            alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout);
        }
    }
}
 
fn main() {
    let mut vec = MyVec::new();
    vec.push(1);
    vec.push(2);
    vec.push(3);
 
    println!("长度: {}, 容量: {}", vec.len(), vec.capacity());
 
    while let Some(val) = vec.pop() {
        println!("弹出: {}", val);
    }
}

7.2 实现双向链表(简化版)

use std::ptr::NonNull;
 
struct Node<T> {
    elem: T,
    next: Option<NonNull<Node<T>>>,
    prev: Option<NonNull<Node<T>>>,
}
 
pub struct LinkedList<T> {
    head: Option<NonNull<Node<T>>>,
    tail: Option<NonNull<Node<T>>>,
    len: usize,
}
 
impl<T> LinkedList<T> {
    pub fn new() -> Self {
        Self {
            head: None,
            tail: None,
            len: 0,
        }
    }
 
    pub fn push_front(&mut self, elem: T) {
        let new_node = Box::new(Node {
            elem,
            next: self.head,
            prev: None,
        });
 
        let new_node = NonNull::new(Box::into_raw(new_node));
 
        match self.head {
            Some(mut head) => unsafe {
                head.as_mut().prev = new_node;
            },
            None => self.tail = new_node,
        }
 
        self.head = new_node;
        self.len += 1;
    }
 
    pub fn pop_front(&mut self) -> Option<T> {
        self.head.map(|node| unsafe {
            let node = Box::from_raw(node.as_ptr());
            self.head = node.next;
 
            match self.head {
                Some(mut head) => head.as_mut().prev = None,
                None => self.tail = None,
            }
 
            self.len -= 1;
            node.elem
        })
    }
}
 
impl<T> Drop for LinkedList<T> {
    fn drop(&mut self) {
        while self.pop_front().is_some() {}
    }
}
 
fn main() {
    let mut list = LinkedList::new();
    list.push_front(1);
    list.push_front(2);
    list.push_front(3);
 
    while let Some(val) = list.pop_front() {
        println!("{}", val);
    }
}

8. 内存布局与repr属性

8.1 repr(C)

与C兼容的内存布局:

#[repr(C)]
struct Point {
    x: f64,
    y: f64,
}
 
#[repr(C)]
struct Rectangle {
    top_left: Point,
    bottom_right: Point,
}

8.2 repr(packed)

去除填充字节,但可能降低性能:

#[repr(packed)]
struct PackedStruct {
    a: u8,
    b: u32,  // 通常会有3字节填充,packed去除了填充
}

8.3 repr(align)

指定对齐方式:

#[repr(align(64))]
struct CacheLineAligned {
    data: [u8; 64],
}
 
fn main() {
    assert_eq!(std::mem::align_of::<CacheLineAligned>(), 64);
}

9. FFI(外部函数接口)

9.1 与C库交互

// 绑定到C标准库
#[link(name = "c")]
extern "C" {
    fn malloc(size: usize) -> *mut c_void;
    fn free(ptr: *mut c_void);
    fn memset(ptr: *mut c_void, value: c_int, size: usize) -> *mut c_void;
}
 
use std::os::raw::{c_void, c_int};
 
fn main() {
    unsafe {
        let ptr = malloc(1024);
        if ptr.is_null() {
            panic!("内存分配失败");
        }
 
        memset(ptr, 0, 1024);
 
        free(ptr);
    }
}

9.2 使用bindgen自动生成绑定

安装bindgen:

cargo install bindgen-cli

生成绑定:

bindgen /usr/include/some_header.h -o src/bindings.rs

9.3 回调函数

C调用Rust回调:

// Rust代码
pub extern "C" fn rust_callback(data: i32) {
    println!("从C收到数据: {}", data);
}
 
// 声明C函数
#[link(name = "some_c_lib")]
extern "C" {
    fn register_callback(cb: extern "C" fn(i32));
    fn trigger_callback();
}
 
fn main() {
    unsafe {
        register_callback(rust_callback);
        trigger_callback();
    }
}

9.4 处理字符串

use std::ffi::{CStr, CString};
use std::os::raw::c_char;
 
fn rust_string_to_c(s: &str) -> CString {
    CString::new(s).expect("字符串包含空字节")
}
 
unsafe fn c_string_to_rust(ptr: *const c_char) -> String {
    CStr::from_ptr(ptr)
        .to_str()
        .expect("无效UTF-8")
        .to_string()
}
 
fn main() {
    let c_string = rust_string_to_c("Hello");
    println!("C字符串: {:?}", c_string);
 
    // 假设从C收到了一个字符串指针
    // let rust_str = unsafe { c_string_to_rust(c_ptr) };
}

10. 内联汇编

Rust支持内联汇编(nightly版本):

#![feature(asm)]
 
fn main() {
    let mut x: u64 = 4;
    unsafe {
        asm!(
            "add {0}, {1}",
            inout(reg) x,
            in(reg) 5,
        );
    }
    assert_eq!(x, 9);
}

稳定版本可以使用外部汇编文件或cc crate:

// build.rs
fn main() {
    cc::Build::new()
        .file("src/asm_code.S")
        .compile("asm_code");
}

11. 未定义行为(UB)

11.1 常见的UB来源

11.2 避免UB的最佳实践

// 1. 始终检查指针有效性
unsafe fn safe_deref(ptr: *const i32) -> Option<i32> {
    if ptr.is_null() {
        return None;
    }
    Some(*ptr)
}
 
// 2. 使用NonNull确保非空
use std::ptr::NonNull;
 
struct SafePtr<T> {
    ptr: NonNull<T>,
}
 
// 3. 使用MaybeUninit处理未初始化内存
use std::mem::MaybeUninit;
 
fn create_array<T, F>(mut f: F) -> [T; 100]
where
    F: FnMut(usize) -> T,
{
    let mut arr: [MaybeUninit<T>; 100] = 
        unsafe { MaybeUninit::uninit().assume_init() };
 
    for i in 0..100 {
        arr[i] = MaybeUninit::new(f(i));
    }
 
    unsafe {
        std::mem::transmute::<_, [T; 100]>(arr)
    }
}

12. Miri:检测UB的工具

Miri是一个Rust解释器,可以检测未定义行为:

rustup component add miri
cargo miri test
// 这段代码会被Miri检测到UB
fn main() {
    let ptr = Box::into_raw(Box::new(1));
    unsafe {
        // 错误:使用已经被释放的内存
        drop(Box::from_raw(ptr));
        println!("{}", *ptr);  // UB!
    }
}

13. 性能优化中的Unsafe

13.1 消除边界检查

fn sum_array(arr: &[i32]) -> i32 {
    let mut sum = 0;
    unsafe {
        // 我们知道索引是安全的
        for i in 0..arr.len() {
            sum += *arr.get_unchecked(i);
        }
    }
    sum
}

13.2 SIMD操作

#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
 
#[target_feature(enable = "avx2")]
unsafe fn add_vectors_avx2(a: &[f32], b: &[f32], c: &mut [f32]) {
    let len = a.len();
    let mut i = 0;
 
    while i + 8 <= len {
        let va = _mm256_loadu_ps(a.as_ptr().add(i));
        let vb = _mm256_loadu_ps(b.as_ptr().add(i));
        let vc = _mm256_add_ps(va, vb);
        _mm256_storeu_ps(c.as_mut_ptr().add(i), vc);
        i += 8;
    }
 
    // 处理剩余元素
    for j in i..len {
        c[j] = a[j] + b[j];
    }
}
 
fn main() {
    let a = vec![1.0f32; 16];
    let b = vec![2.0f32; 16];
    let mut c = vec![0.0f32; 16];
 
    unsafe {
        add_vectors_avx2(&a, &b, &mut c);
    }
 
    println!("{:?}", c);
}

14. 安全抽象设计原则

14.1 最小化unsafe代码

// 不好:整个函数都是unsafe
pub unsafe fn process_data(data: *mut u8, len: usize) {
    // ...
}
 
// 好:将unsafe封装在内部
pub fn process_data(data: &mut [u8]) {
    unsafe {
        // 只在必要时使用unsafe
    }
}

14.2 文档化安全不变量

/// # Safety
/// 
/// 调用者必须保证:
/// - `ptr`指向有效的内存
/// - `ptr`对齐到T的对齐要求
/// - `ptr`指向的内存不会被同时访问
pub unsafe fn read_data<T>(ptr: *const T) -> T {
    ptr::read(ptr)
}

14.3 使用assert!进行调试检查

pub unsafe fn read_unchecked<T>(slice: &[T], index: usize) -> &T {
    debug_assert!(index < slice.len(), "索引越界");
    slice.get_unchecked(index)
}

小结

本章深入探讨了Unsafe Rust:

使用Unsafe Rust的关键原则:

Unsafe Rust是一把双刃剑,它赋予你极致的控制权和性能,但也要求你对内存模型和未定义行为有深入理解。合理使用unsafe,你可以在保持Rust安全保证的同时完成最底层的工作。