bigywater.tech
Rust · 120

unsafe 的安全边界

unsafe 到底解锁了什么?它有没有关掉借用检查?怎么把 unsafe 封装成安全接口?这篇逐条编译验证 unsafe 的边界,并写一个最小的安全抽象示范「前提条件检查 + 最小 unsafe 块」。

5 种
unsafe 解锁的能力数(裸指针、unsafe fn、static mut、unsafe trait、union)
E0133
在 unsafe 块外使用这些能力时的错误码
E0502
unsafe 块里仍然会被借用检查器拒绝的错误

五种超能力

实测 · 本机执行输出
  【解引用裸指针】编译失败:['error[E0133]: dereference of raw pointer is unsafe and requires unsafe block']
  【调用 unsafe 函数】编译失败:['error[E0133]: call to unsafe function `danger` is unsafe and requires unsafe block']
  【访问 static mut】编译失败:['error[E0133]: use of mutable static is unsafe and requires unsafe block']
  【访问 union 字段】编译失败:['error[E0133]: access to union field is unsafe and requires unsafe block']
  源码:
      1 | unsafe trait Zeroable {}
      2 | struct S; impl Zeroable for S {}
      3 | fn main() {}
  编译:失败
    error[E0200]: the trait `Zeroable` requires an `unsafe impl` declaration
     --> <实验目录>/src/u120b.rs:2:11
      |
    2 | struct S; impl Zeroable for S {}
      |           ^^^^^^^^^^^^^^^^^^^
      |
      = note: the trait `Zeroable` enforces invariants that the compiler can't check. Review the trait documentation and make sure this implementation upholds those invariants before adding the `unsafe` keyword
    help: add `unsafe` to this trait implementation
      |
    2 | struct S; unsafe impl Zeroable for S {}
      |           ++++++
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0200`.

裸指针的使用见原始指针解引用。

放进 unsafe 块

实测 · 本机执行输出
  编译:成功;警告:
    warning: trait `Zeroable` is never used
     --> <实验目录>/src/u120c.rs:4:14
      |
    4 | unsafe trait Zeroable {} unsafe impl Zeroable for u32 {}
      |              ^^^^^^^^
      |
      = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
    
    warning: 1 warning emitted
  运行(退出码 0):
    解引用裸指针 = 5
    调用 unsafe fn  = 5
    static mut N    = 1
    union 字段 b    = 1(把 u32 0x3f800000 当 f32 读)
    unsafe impl Zeroable for u32:编译通过(实现 unsafe trait 本身也要写 unsafe impl)

借用检查仍然有效

实测 · 本机执行输出
  源码:
      1 | fn main() { let mut v = vec![1]; let first = &v[0]; unsafe { v.push(2); } println!("{}", first); }
  编译:失败
    warning: unnecessary `unsafe` block
     --> <实验目录>/src/u120d.rs:1:53
      |
    1 | fn main() { let mut v = vec![1]; let first = &v[0]; unsafe { v.push(2); } println!("{}", first); }
      |                                                     ^^^^^^ unnecessary `unsafe` block
      |
      = note: `#[warn(unused_unsafe)]` (part of `#[warn(unused)]`) on by default
    
    error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
     --> <实验目录>/src/u120d.rs:1:62
      |
    1 | fn main() { let mut v = vec![1]; let first = &v[0]; unsafe { v.push(2); } println!("{}", first); }
      |                                               -              ^^^^^^^^^                   ----- immutable borrow later used here
      |                                               |              |
      |                                               |              mutable borrow occurs here
      |                                               immutable borrow occurs here
    
    error: aborting due to 1 previous error; 1 warning emitted
    
    For more information about this error, try `rustc --explain E0502`.

借用规则见不可变借用与可变借用。

安全抽象

实测 · 本机执行输出
  编译:成功
  运行(退出码 101):
    安全接口的结果:[10, 2, 30, 4, 5]
    
    thread 'main' (7328087) panicked at <实验目录>/src/u120e.rs:3:5:
    mid 必须 <= 长度
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

forbid 与 2024 edition

实测 · 本机执行输出
  源码:
      1 | #![forbid(unsafe_code)]
      2 | fn main() { let x = 1; let p = &x as *const i32; let _ = unsafe { *p }; }
  编译:失败
    error: usage of an `unsafe` block
     --> <实验目录>/src/u120f.rs:2:58
      |
    2 | fn main() { let x = 1; let p = &x as *const i32; let _ = unsafe { *p }; }
      |                                                          ^^^^^^^^^^^^^
      |
    note: the lint level is defined here
     --> <实验目录>/src/u120f.rs:1:11
      |
    1 | #![forbid(unsafe_code)]
      |           ^^^^^^^^^^^
    
    error: aborting due to 1 previous error
  源码:
      1 | unsafe fn f(p: *const i32) -> i32 { *p }
      2 | fn main() { let x = 1; println!("{}", unsafe { f(&x) }); }
  编译:成功;警告:
    warning[E0133]: dereference of raw pointer is unsafe and requires unsafe block
     --> <实验目录>/src/u120g.rs:1:37
      |
    1 | unsafe fn f(p: *const i32) -> i32 { *p }
      |                                     ^^ dereference of raw pointer
      |
      = note: raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior
    note: an unsafe function restricts its caller, but its body is safe by default
     --> <实验目录>/src/u120g.rs:1:1
      |
    1 | unsafe fn f(p: *const i32) -> i32 { *p }
      | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      = note: for more information, see <https://doc.rust-lang.org/edition-guide/rust-2024/unsafe-op-in-unsafe-fn.html>
      = note: `#[warn(unsafe_op_in_unsafe_fn)]` (part of `#[warn(rust_2024_compatibility)]`) on by default
    
    warning: 1 warning emitted
    
    For more information about this error, try `rustc --explain E0133`.
  运行(退出码 0):
    1

跨线程的 unsafe impl 见Send 与 Sync。

分步演示

unsafe 的边界
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动
    • 没有验证:Miri 检查 unsafe 代码(需要 nightly)、unsafe 代码里的未定义行为的实际后果、extern 块里 unsafe 的写法在其它 edition 的差别、cargo-geiger 之类统计 unsafe 的工具
    京ICP备2026067502号-1京公网安备11010502063487号