Rust 系统编程语言完全教程 / 第22章:Unsafe Rust
第22章:Unsafe Rust
22.1 为什么需要 Unsafe
Rust 的安全保证在某些场景下过于保守,unsafe 允许你绕过部分检查:
| 能力 | Safe Rust | Unsafe Rust |
|---|
| 解引用裸指针 | ❌ | ✅ |
| 调用 unsafe 函数 | ❌ | ✅ |
| 访问可变静态变量 | ❌ | ✅ |
| 实现 unsafe trait | ❌ | ✅ |
| 访问 union 字段 | ❌ | ✅ |
注意: unsafe 不会关闭借用检查器或其他安全检查,它只解锁上述五种能力。
22.2 裸指针(Raw Pointer)
创建与解引用
fn main() {
let mut num = 42;
// 创建裸指针(safe)
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
println!("r1 = {:?}, r2 = {:?}", r1, r2);
// 解引用裸指针(unsafe)
unsafe {
println!("*r1 = {}", *r1);
*r2 = 100;
println!("*r2 = {}", *r2);
}
println!("num = {}", num); // 100
}
裸指针的特点
| 特性 | 引用 | 裸指针 |
|---|
| 空值 | ❌ 不允许 | ✅ 可以为空 |
| 保证有效 | ✅ | ❌ 不保证 |
| 自动释放 | ✅ | ❌ |
| 借用规则 | ✅ 检查 | ❌ 不检查 |
| 可变性 | 需声明 | 不需要 |
实用场景:拆分借用
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 {
(
std::slice::from_raw_parts_mut(ptr, mid),
std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
}
fn main() {
let mut data = vec![1, 2, 3, 4, 5, 6];
let (left, right) = split_at_mut(&mut data, 3);
println!("左: {:?}", left); // [1, 2, 3]
println!("右: {:?}", right); // [4, 5, 6]
}
22.3 unsafe 函数与 trait
unsafe 函数
/// 从裸指针读取值
/// 调用者必须确保指针有效且已对齐
unsafe fn dangerous_read(ptr: *const i32) -> i32 {
*ptr
}
fn main() {
let num = 42;
let ptr = &num as *const i32;
let value = unsafe { dangerous_read(ptr) };
println!("值: {}", value);
}
unsafe trait
/// 标记类型可以安全地跨线程共享
unsafe trait Send {}
/// 标记类型可以安全地从多线程引用
unsafe trait Sync {}
// 手动实现 unsafe trait
unsafe trait MyUnsafeTrait {
fn dangerous_method(&self);
}
struct MyType;
unsafe impl MyUnsafeTrait for MyType {
fn dangerous_method(&self) {
println!("实现 unsafe trait 方法");
}
}
fn main() {
let obj = MyType;
obj.dangerous_method();
}
22.4 FFI(外部函数接口)
调用 C 函数
// 声明外部 C 函数
extern "C" {
fn abs(input: i32) -> i32;
fn sqrt(input: f64) -> f64;
fn strlen(s: *const std::ffi::c_char) -> usize;
}
fn main() {
// 调用 C 标准库函数
unsafe {
println!("abs(-5) = {}", abs(-5));
println!("sqrt(4.0) = {}", sqrt(4.0));
}
// 调用 strlen
let c_str = std::ffi::CString::new("Hello, C!").unwrap();
unsafe {
let len = strlen(c_str.as_ptr());
println!("strlen = {}", len);
}
}
暴露 Rust 函数给 C
// 导出给 C 的函数
#[no_mangle]
pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
a + b
}
#[no_mangle]
pub extern "C" fn rust_greet(name: *const std::ffi::c_char) {
let name = unsafe {
std::ffi::CStr::from_ptr(name).to_str().unwrap_or("unknown")
};
println!("Hello, {}!", name);
}
fn main() {
// 在 Rust 中也可以调用
println!("2 + 3 = {}", rust_add(2, 3));
let name = std::ffi::CString::new("Rust").unwrap();
rust_greet(name.as_ptr());
}
字符串转换
use std::ffi::{CString, CStr};
use std::os::raw::c_char;
fn main() {
// Rust String → C 字符串
let rust_string = "Hello, C!";
let c_string = CString::new(rust_string).expect("CString::new failed");
let c_ptr: *const c_char = c_string.as_ptr();
// C 字符串 → Rust &str
unsafe {
let c_str = CStr::from_ptr(c_ptr);
let rust_str = c_str.to_str().expect("Invalid UTF-8");
println!("Rust: {}", rust_str);
}
// 注意:CString 拥有数据,CStr 只是借用
// CStr::from_ptr 要求指针指向有效的以 null 结尾的字符串
}
22.5 Union 类型
#[repr(C)]
union IntOrFloat {
i: i32,
f: f32,
}
fn main() {
let mut value = IntOrFloat { i: 42 };
// 访问 union 字段需要 unsafe
unsafe {
println!("作为整数: {}", value.i);
}
value.f = 3.14;
unsafe {
println!("作为浮点: {}", value.f);
// 读取之前的字段是未定义行为
// println!("作为整数: {}", value.i); // 位模式不同
}
}
22.6 实现安全抽象
Vec::push 的简化实现
use std::ptr;
struct MyVec<T> {
ptr: *mut T,
len: usize,
cap: usize,
}
impl<T> MyVec<T> {
fn new() -> Self {
Self {
ptr: ptr::NonNull::dangling().as_ptr(),
len: 0,
cap: 0,
}
}
fn push(&mut self, value: T) {
if self.len == self.cap {
self.grow();
}
unsafe {
ptr::write(self.ptr.add(self.len), value);
}
self.len += 1;
}
fn grow(&mut self) {
let new_cap = if self.cap == 0 { 4 } else { self.cap * 2 };
let new_ptr = unsafe {
std::alloc::realloc(
self.ptr as *mut u8,
std::alloc::Layout::array::<T>(self.cap).unwrap(),
std::mem::size_of::<T>() * new_cap,
) as *mut T
};
self.ptr = new_ptr;
self.cap = new_cap;
}
fn get(&self, index: usize) -> Option<&T> {
if index < self.len {
unsafe { Some(&*self.ptr.add(index)) }
} else {
None
}
}
}
fn main() {
let mut v = MyVec::new();
v.push(1);
v.push(2);
v.push(3);
for i in 0..3 {
println!("v[{}] = {:?}", i, v.get(i));
}
}
22.7 unsafe 最佳实践
封装 unsafe 代码
// ✅ 好的做法:用安全 API 封装 unsafe 代码
pub fn safe_split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
assert!(mid <= slice.len(), "索引越界");
// unsafe 被封装在安全函数内部
unsafe { split_at_mut_unchecked(slice, mid) }
}
unsafe fn split_at_mut_unchecked(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
let ptr = slice.as_mut_ptr();
(
std::slice::from_raw_parts_mut(ptr, mid),
std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
fn main() {
let mut data = vec![1, 2, 3, 4, 5];
let (a, b) = safe_split_at_mut(&mut data, 2);
println!("a={:?}, b={:?}", a, b);
}
使用 Miri 检查
# 安装 Miri(unsafe 代码检查工具)
rustup +nightly component add miri
# 运行 Miri
cargo +nightly miri test
cargo +nightly miri run
22.8 业务场景示例
性能优化:手动内存管理
use std::alloc::{alloc, dealloc, Layout};
struct Buffer {
ptr: *mut u8,
layout: Layout,
}
impl Buffer {
fn new(size: usize) -> Self {
let layout = Layout::array::<u8>(size).unwrap();
let ptr = unsafe { alloc(layout) };
if ptr.is_null() {
std::alloc::handle_alloc_error(layout);
}
// 清零
unsafe {
std::ptr::write_bytes(ptr, 0, size);
}
Self { ptr, layout }
}
fn as_slice(&self) -> &[u8] {
unsafe {
std::slice::from_raw_parts(self.ptr, self.layout.size())
}
}
}
impl Drop for Buffer {
fn drop(&mut self) {
unsafe {
dealloc(self.ptr, self.layout);
}
}
}
fn main() {
let buf = Buffer::new(1024);
println!("缓冲区大小: {} 字节", buf.as_slice().len());
println!("前10字节: {:?}", &buf.as_slice()[..10]);
}
22.9 本章小结
| 要点 | 说明 |
|---|
| unsafe | 解锁五种额外能力,不关闭借用检查 |
| 裸指针 | *const T 和 *mut T |
| FFI | extern “C” 调用 C 代码 |
| Union | 类似 C 的联合体 |
| 安全封装 | 用安全 API 包装 unsafe 代码 |
| Miri | unsafe 代码检查工具 |
扩展阅读
- Rustonomicon — unsafe 深入
- The Unsafe Book — 官方教程
- Rust FFI Guide — FFI 详解