Skip to content

完整示例

示例 1:自定义 Vec

rust
use 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));
}
▶ Run

示例 2:包装 C 库

rust
use 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());
}
▶ Run

常见错误

错误 1:悬垂指针

rust
fn main() {
    let r;
    {
        let x = 5;
        r = &x as *const i32;
    }

    unsafe {
        // ❌ 错误:x 已释放
        // println!("r: {}", *r);  // 未定义行为
    }
}
▶ Run

错误 2:空指针解引用

rust
fn main() {
    let ptr: *const i32 = std::ptr::null();

    unsafe {
        // ❌ 错误:空指针解引用
        // println!("{}", *ptr);  // 崩溃
    }
}
▶ Run

错误 3:数据竞争

rust
static mut DATA: i32 = 0;

fn main() {
    // ❌ 错误:多线程访问可变静态变量
    // unsafe { DATA += 1; }  // 数据竞争
}
▶ Run

错误 4:无效内存访问

rust
fn main() {
    let arr = [1, 2, 3];
    let ptr = arr.as_ptr();

    unsafe {
        // ❌ 错误:越界访问
        // println!("{}", *ptr.add(10));
    }
}
▶ Run

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

第 25 章:宏