宏、unsafe 与 FFI advanced 30 分钟 更新 2026-06-15

unsafe 基础

理解 unsafe 的能力边界,以及如何封装成安全接口。

unsafe 基础

你会学到什么

  • unsafe 解锁五种能力,其中最常见的是解引用裸指针。
  • unsafe 不关闭借用检查,它只是让你为某些操作负责。
  • 好的做法是把 unsafe 封装在安全接口内,并写上 // SAFETY: 说明。

最小示例

fn double_in_place(value: &mut i32) {
    let ptr: *mut i32 = value;
    // SAFETY: ptr 来自有效的可变引用。
    unsafe { *ptr *= 2; }
}

运行代码

cd examples
cargo run -p rt_40_unsafe
cargo test -p rt_40_unsafe

代码讲解

经典例子:借用检查器无法证明从一个切片切出的两半互不重叠,标准库的 split_at_mut 内部就用了 unsafe:

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);
    // SAFETY: mid <= len,两个子切片不重叠且都在原范围内。
    unsafe {
        (
            std::slice::from_raw_parts_mut(ptr, mid),
            std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

关键在于:对外的函数签名是完全安全的,调用方无需写 unsafe;不变量(mid <= len、不重叠)由实现者用 assert! 和注释来保证。

常见错误

以为 unsafe 会“关闭检查、随便写”。它只放开五件事:解引用裸指针、调用 unsafe 函数、访问/修改可变静态变量、实现 unsafe trait、访问 union 字段。其余规则照旧。

练习

  • 用裸指针实现一个 swap(a: &mut i32, b: &mut i32)
  • split_at_mut 加一个 mid > len 的测试,确认会 panic。

小结

unsafe 是“我来担保”的承诺,应尽量小、尽量封装,并用 // SAFETY: 注释写清依赖的不变量。

下一步

unsafe 的一个主要用途是和 C 互操作。下一章学习 FFI。

完整示例代码

下面是 examples/40_unsafe/ 的完整源码。无需 clone 仓库,直接在页面上阅读、复制、对照运行。

examples/40_unsafe/src/main.rs
//! unsafe:在安全抽象内部使用裸指针等不安全操作。

/// 经典例子:借用检查器无法证明两个可变切片不重叠,
/// 用裸指针 + unsafe 实现,但对外暴露完全安全的接口。
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, "mid 超出长度");

    // SAFETY: mid <= len,两个子切片不重叠,且都落在原切片范围内。
    unsafe {
        (
            std::slice::from_raw_parts_mut(ptr, mid),
            std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

/// 通过裸指针修改值(演示解引用裸指针是 unsafe 操作)。
fn double_in_place(value: &mut i32) {
    let ptr: *mut i32 = value;
    // SAFETY: ptr 来自一个有效的可变引用,非空且对齐。
    unsafe {
        *ptr *= 2;
    }
}

fn main() {
    let mut data = [1, 2, 3, 4, 5];
    let (left, right) = split_at_mut(&mut data, 2);
    println!("left = {left:?}, right = {right:?}");

    let mut value = 21;
    double_in_place(&mut value);
    println!("doubled = {value}");
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn splits_in_two() {
        let mut data = [1, 2, 3, 4, 5];
        let (left, right) = split_at_mut(&mut data, 2);
        assert_eq!(left, &mut [1, 2]);
        assert_eq!(right, &mut [3, 4, 5]);
    }

    #[test]
    fn doubles_value() {
        let mut value = 21;
        double_in_place(&mut value);
        assert_eq!(value, 42);
    }
}
examples/40_unsafe/Cargo.toml
[package]
name = "rt_40_unsafe"
version.workspace = true
edition.workspace = true
publish.workspace = true