完整示例
示例 1:自定义 Vec
rust
▶ Runuse std::alloc::{alloc, dealloc, Layout};
use std::ptr;
struct MyVec<T> {
ptr: *mut T,
len: usize,
cap: usize,
}
impl<T> MyVec<T> {
fn new() -> Self {
assert!(std::mem::size_of::<T>() > 0);
MyVec {
ptr: ptr::null_mut(),
len: 0,
cap: 0,
}
}
fn push(&mut self, value: T) {
if self.len == self.cap {
let new_cap = if self.cap == 0 { 1 } else { self.cap * 2 };
self.grow(new_cap);
}
unsafe {
ptr::write(self.ptr.add(self.len), value);
}
self.len += 1;
}
fn grow(&mut self, new_cap: usize) {
unsafe {
let new_layout = Layout::array::<T>(new_cap).unwrap();
let new_ptr = if self.cap == 0 {
alloc(new_layout) as *mut T
} else {
let old_layout = Layout::array::<T>(self.cap).unwrap();
std::alloc::realloc(self.ptr as *mut u8, old_layout, new_layout.size()) 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
}
}
}
impl<T> Drop for MyVec<T> {
fn drop(&mut self) {
unsafe {
for i in 0..self.len {
ptr::drop_in_place(self.ptr.add(i));
}
if self.cap > 0 {
let layout = Layout::array::<T>(self.cap).unwrap();
dealloc(self.ptr as *mut u8, layout);
}
}
}
}
fn main() {
let mut v = MyVec::<i32>::new();
v.push(1);
v.push(2);
v.push(3);
println!("v[0] = {:?}", v.get(0));
println!("v[1] = {:?}", v.get(1));
}示例 2:包装 C 库
rust
▶ Runuse std::os::raw::{c_int, c_void};
use std::ptr;
// 模拟 C 库函数
extern "C" {
fn malloc(size: usize) -> *mut c_void;
fn free(ptr: *mut c_void);
}
// 安全包装
pub struct Buffer {
ptr: *mut u8,
len: usize,
}
impl Buffer {
pub fn new(len: usize) -> Self {
unsafe {
let ptr = malloc(len) as *mut u8;
if ptr.is_null() {
panic!("内存分配失败");
}
// 初始化内存
ptr::write_bytes(ptr, 0, len);
Buffer { ptr, len }
}
}
pub fn as_slice(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
}
pub fn as_mut_slice(&mut self) -> &mut [u8] {
unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
}
}
impl Drop for Buffer {
fn drop(&mut self) {
unsafe {
free(self.ptr as *mut c_void);
}
}
}
fn main() {
let mut buffer = Buffer::new(10);
buffer.as_mut_slice().copy_from_slice(b"hello");
println!("{:?}", buffer.as_slice());
}常见错误
错误 1:悬垂指针
rust
▶ Runfn main() {
let r;
{
let x = 5;
r = &x as *const i32;
}
unsafe {
// ❌ 错误:x 已释放
// println!("r: {}", *r); // 未定义行为
}
}错误 2:空指针解引用
rust
▶ Runfn main() {
let ptr: *const i32 = std::ptr::null();
unsafe {
// ❌ 错误:空指针解引用
// println!("{}", *ptr); // 崩溃
}
}错误 3:数据竞争
rust
▶ Runstatic mut DATA: i32 = 0;
fn main() {
// ❌ 错误:多线程访问可变静态变量
// unsafe { DATA += 1; } // 数据竞争
}错误 4:无效内存访问
rust
▶ Runfn main() {
let arr = [1, 2, 3];
let ptr = arr.as_ptr();
unsafe {
// ❌ 错误:越界访问
// println!("{}", *ptr.add(10));
}
}Unsafe 编程指南
最佳实践
┌─────────────────────────────────────────────────────┐
│ Unsafe 最佳实践 │
├─────────────────────────────────────────────────────┤
│ │
│ 1. 最小化 │
│ • 只在必要时使用 unsafe │
│ • 尽可能缩小 unsafe 块的范围 │
│ │
│ 2. 封装 │
│ • 在安全接口内封装 unsafe │
│ • 对外暴露安全的 API │
│ │
│ 3. 文档 │
│ • 说明为什么 unsafe 是安全的 │
│ • 列出所有需要维护的不变量 │
│ │
│ 4. 测试 │
│ • 充分测试 unsafe 代码 │
│ • 使用 Miri 检测未定义行为 │
│ │
│ 5. 审计 │
│ • 标记 unsafe 代码块 │
│ • 定期审查 unsafe 代码 │
│ │
└─────────────────────────────────────────────────────┘使用 Miri 检测
bash
# 安装 Miri
rustup component add miri
# 运行 Miri
cargo miri run
cargo miri test
# Miri 可以检测:
# • 悬垂指针
# • 空指针解引用
# • 越界访问
# • 数据竞争练习
练习 1:安全包装
创建一个安全的裸指针包装类型,确保不会悬垂。
练习 2:FFI 调用
使用 FFI 调用一个系统函数(如 libc 中的函数)。
练习 3:简单内存池
实现一个简单的内存池,使用 unsafe 管理内存。
小结
本章我们学习了:
- ✅ 裸指针的创建和解引用
- ✅ 不安全函数的定义和调用
- ✅ 外部函数接口(FFI)
- ✅ 可变静态变量
- ✅ 不安全 Trait
- ✅ Union 类型
- ✅ Unsafe 最佳实践
Unsafe 规则速查
| 规则 | 说明 |
|---|---|
| 最小化 | 只在必要时使用 |
| 封装 | 安全接口封装 unsafe |
| 文档 | 说明安全性保证 |
| 测试 | 充分测试,使用 Miri |