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