Rust 安全编程:unsafe 与智能指针使用

1. unsafe 关键字的作用与限制

Rust 默认保证内存安全,但某些底层操作需通过 unsafe 显式声明:

  • 允许的操作
    • 解引用裸指针(*const T/*mut T
    • 调用 unsafe 函数
    • 访问/修改可变静态变量
    • 实现 unsafe trait
  • 核心原则
    • unsafe 代码封装在安全抽象层内
    • 外部调用应保持安全接口
    • 遵循所有权和借用规则

示例:安全封装指针解引用

fn safe_deref(ptr: *const i32) -> Option<i32> {
    if ptr.is_null() {
        None
    } else {
        unsafe { Some(*ptr) } // 封装边界检查
    }
}

2. 智能指针与内存管理

智能指针通过元数据管理资源,常见类型:

类型 所有权 线程安全 典型用例
Box<T> 单一 堆分配、递归类型
Rc<T> 共享 单线程引用计数
Arc<T> 共享 多线程引用计数
RefCell<T> 内部可变 运行时借用检查
关键特性:
  • Deref trait:实现隐式解引用
    let x = Box::new(42);
    assert_eq!(*x, 42); // 自动调用 Deref::deref
    

  • Drop trait:自动释放资源
    impl<T> Drop for MyPointer<T> {
        fn drop(&mut self) {
            unsafe { libc::free(self.ptr) }; // 自定义释放逻辑
        }
    }
    

3. unsafe 与智能指针结合实践

场景:创建自定义智能指针

struct SafeVec<T> {
    ptr: *mut T,
    len: usize,
}

impl<T> SafeVec<T> {
    pub fn new() -> Self {
        Self {
            ptr: std::ptr::null_mut(),
            len: 0,
        }
    }
    
    pub fn push(&mut self, item: T) {
        unsafe {
            // 内存分配/扩容逻辑
            let new_layout = /* 计算新布局 */;
            self.ptr = /* 重新分配内存 */;
            std::ptr::write(self.ptr.add(self.len), item);
            self.len += 1;
        }
    }
}

impl<T> Drop for SafeVec<T> {
    fn drop(&mut self) {
        unsafe {
            // 释放所有元素
            for i in 0..self.len {
                std::ptr::drop_in_place(self.ptr.add(i));
            }
            libc::free(self.ptr as *mut libc::c_void);
        }
    }
}

4. 安全实践准则
  1. 最小化 unsafe 范围

    // 推荐:局部化 unsafe 块
    let value = unsafe { *ptr }; 
    

  2. 智能指针优先原则

    • Box 替代裸指针堆分配
    • Rc/Arc 管理共享所有权
    • Mutex 替代手动同步
  3. 边界检查

    unsafe {
        if index < len {
            *ptr.add(index) = value; // 安全访问
        }
    }
    

  4. 内存模型一致性

    • 确保指针生命周期不越界
    • 避免数据竞争(Data Race)
    • 使用 MaybeUninit 处理未初始化内存
5. 典型错误模式
// 危险:悬垂指针
let dangling_ptr = unsafe { &*ptr }; 
// 正确:绑定生命周期
let safe_ref: &'a T = unsafe { &*ptr }; 

// 危险:未同步的跨线程访问
unsafe { static mut COUNTER: i32 = 0; }
// 正确:使用 Atomic 类型
use std::sync::atomic::AtomicI32;
static SAFE_COUNTER: AtomicI32 = AtomicI32::new(0);

总结:Rust 的安全哲学要求将 unsafe 操作封装在受控抽象层内,通过智能指针管理资源生命周期。实践中应遵循:

  1. 优先使用标准库智能指针
  2. unsafe 代码编写完备的文档和测试
  3. 通过类型系统约束不安全操作

最终目标:在安全边界内发挥底层控制能力,实现 零成本抽象

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐