🦀 第19课:unsafe Rust

实用工具 第19/25课

Rust的安全保证是它的核心优势,但有时候你需要绕过这些检查:unsafe允许你执行Rust正常不允许的五种操作。这是Rust给高级开发者的"逃生舱口"。

⚠️ unsafe的五种超能力

  1. 解引用裸指针 — *const T / *mut T
  2. 调用unsafe函数 — 包括C函数和Rust的unsafe fn
  3. 访问可变静态变量 — static mut
  4. 实现unsafe trait — 如Send/Sync
  5. 访问union字段
fn main() {
    // 1. 裸指针
    let mut num = 5;
    let r1 = &num as *const i32;  // 不可变裸指针
    let r2 = &mut num as *mut i32;  // 可变裸指针
    
    unsafe {
        println!("r1指向: {}", *r1);  // 解引用裸指针必须在unsafe中
        *r2 = 10;  // 通过裸指针修改
        println!("修改后: {}", *r1);
    }
    
    // 2. unsafe函数
    unsafe fn dangerous() -> i32 { 42 }
    
    let result = unsafe { dangerous() };
    println!("unsafe函数结果: {}", result);
    
    // 3. 可变静态变量
    static mut COUNTER: u32 = 0;
    
    unsafe {
        COUNTER += 1;
        println!("计数器: {}", COUNTER);
    }
    
    // 4. split_at_mut的安全实现
    fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
        let len = values.len();
        assert!(mid <= len);
        // 安全: 两个切片不重叠
        unsafe {
            let ptr = values.as_mut_ptr();
            (std::slice::from_raw_parts_mut(ptr, mid),
             std::slice::from_raw_parts_mut(ptr.add(mid), len - mid))
        }
    }
    
    let mut arr = [1, 2, 3, 4, 5, 6];
    let (left, right) = split_at_mut(&mut arr, 3);
    println!("左: {:?}, 右: {:?}", left, right);
}
r1指向: 5 修改后: 10 unsafe函数结果: 42 计数器: 1 左: [1, 2, 3], 右: [4, 5, 6]

✅ 验证通过

🏗️ 综合实战:安全的向量包装

use std::ptr::NonNull;

struct MyVec<T> {
    ptr: NonNull<T>,
    len: usize,
    capacity: usize,
}

impl<T> MyVec<T> {
    fn new() -> Self {
        MyVec { ptr: NonNull::dangling(), len: 0, capacity: 0 }
    }
    
    fn with_capacity(capacity: usize) -> Self {
        if capacity == 0 {
            return Self::new();
        }
        let ptr = unsafe {
            let layout = std::alloc::Layout::array::(capacity).unwrap();
            let ptr = std::alloc::alloc(layout) as *mut T;
            NonNull::new(ptr).expect("分配失败")
        };
        MyVec { ptr, len: 0, capacity }
    }
    
    fn push(&mut self, value: T) {
        assert!(self.len < self.capacity, "容量已满");
        unsafe {
            std::ptr::write(self.ptr.as_ptr().add(self.len), value);
        }
        self.len += 1;
    }
    
    fn get(&self, index: usize) -> Option<&T> {
        if index < self.len {
            unsafe { Some(&*self.ptr.as_ptr().add(index)) }
        } else {
            None
        }
    }
    
    fn len(&self) -> usize { self.len }
}

impl<T> Drop for MyVec<T> {
    fn drop(&mut self) {
        if self.capacity > 0 {
            unsafe {
                for i in 0..self.len {
                    std::ptr::drop_in_place(self.ptr.as_ptr().add(i));
                }
                let layout = std::alloc::Layout::array::(self.capacity).unwrap();
                std::alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout);
            }
        }
    }
}

fn main() {
    let mut v: MyVec<i32> = MyVec::with_capacity(5);
    v.push(10); v.push(20); v.push(30);
    for i in 0..v.len() {
        println!("v[{}] = {}", i, v.get(i).unwrap());
    }
}
v[0] = 10 v[1] = 20 v[2] = 30

✅ 验证通过

📝 练习

练习1:FNV哈希

使用裸指针实现FNV-1a哈希函数,比较与标准库的性能。

练习2:自引用结构

使用Pin和裸指针安全地实现自引用结构体。

🏆 本课成就

🔧 unsafe安全抽象模式

pub struct SafeSplit<'a, T> {
    first: &'a mut [T],
    second: &'a mut [T],
}

impl<'a, T> SafeSplit<'a, T> {
    pub fn new(slice: &'a mut [T], mid: usize) -> Option {
        if mid > slice.len() { return None; }
        let ptr = slice.as_mut_ptr();
        let len = slice.len();
        unsafe {
            Some(SafeSplit {
                first: std::slice::from_raw_parts_mut(ptr, mid),
                second: std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
            })
        }
    }
}

fn main() {
    let mut arr = [1, 2, 3, 4, 5, 6];
    let split = SafeSplit::new(&mut arr, 3).unwrap();
    println!("左: {:?}, 右: {:?}", split.first, split.second);
    println!("unsafe安全封装: 内部unsafe,外部安全API");
}
左: [1, 2, 3], 右: [4, 5, 6] unsafe安全封装: 内部unsafe,外部安全API

✅ 验证通过

📊 unsafe使用场景决策树

// 需要unsafe的场景:
// 1. FFI(调用C代码)           → 必须
// 2. 裸指针解引用              → 必须
// 3. 可变静态变量              → 必须
// 4. 性能关键路径(避免边界检查) → 可选但需benchmark
// 5. 实现底层数据结构          → 必须
//
// 不需要unsafe的替代方案:
// • 边界检查 → 使用get()/get_mut()而非[]
// • 类型转换 → 使用From/TryFrom
// • 并发 → 使用Arc/Mutex/Channel
// • 可变性 → 使用RefCell

fn main() {
    // 安全的替代方案
    let v = vec![1, 2, 3];
    
    // ❌ 不安全: v[100] 可能panic
    // ✅ 安全: v.get(100) 返回None
    
    match v.get(100) {
        Some(val) => println!("值: {}", val),
        None => println!("索引越界,安全处理"),
    }
    
    // unsafe的安全封装原则
    println!("
unsafe安全封装原则:");
    println!("1. unsafe块尽量小");
    println!("2. 封装为安全的公开API");
    println!("3. 在文档中说明安全条件");
    println!("4. 使用SAFETY注释标记安全推理");
    
    // SAFETY: 标准库中的安全注释模式
    // // SAFETY: `ptr` is valid for `len` elements because ...
    // unsafe { std::slice::from_raw_parts(ptr, len) }
}
索引越界,安全处理 unsafe安全封装原则: 1. unsafe块尽量小 2. 封装为安全的公开API 3. 在文档中说明安全条件 4. 使用SAFETY注释标记安全推理

✅ 验证通过

🏗️ 综合实战:自定义Vec

use std::ptr::NonNull;

struct MyVec {
    ptr: NonNull,
    len: usize,
    capacity: usize,
}

impl MyVec {
    fn new() -> Self {
        MyVec { ptr: NonNull::dangling(), len: 0, capacity: 0 }
    }
    fn with_capacity(capacity: usize) -> Self {
        if capacity == 0 { return Self::new(); }
        let ptr = unsafe {
            let layout = std::alloc::Layout::array::(capacity).unwrap();
            let p = std::alloc::alloc(layout) as *mut T;
            NonNull::new(p).expect("allocation failed")
        };
        MyVec { ptr, len: 0, capacity }
    }
    fn push(&mut self, value: T) {
        assert!(self.len < self.capacity, "capacity exceeded");
        unsafe { std::ptr::write(self.ptr.as_ptr().add(self.len), value); }
        self.len += 1;
    }
    fn get(&self, i: usize) -> Option<&T> {
        if i < self.len { unsafe { Some(&*self.ptr.as_ptr().add(i)) } } else { None }
    }
    fn len(&self) -> usize { self.len }
}

impl Drop for MyVec {
    fn drop(&mut self) {
        if self.capacity > 0 {
            unsafe {
                for i in 0..self.len { std::ptr::drop_in_place(self.ptr.as_ptr().add(i)); }
                let layout = std::alloc::Layout::array::(self.capacity).unwrap();
                std::alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout);
            }
        }
    }
}

fn main() {
    let mut v: MyVec = MyVec::with_capacity(5);
    v.push("Hello".to_string());
    v.push("Rust".to_string());
    v.push("unsafe".to_string());
    for i in 0..v.len() { println!("v[{}] = {}", i, v.get(i).unwrap()); }
}
v[0] = Hello v[1] = Rust v[2] = unsafe

✅ 验证通过

📋 unsafe审计清单

unsafe代码审查要点

🔧 Miri检测工具

// Miri: Rust未定义行为检测器
// rustup toolchain install nightly
// cargo +nightly miri test
// 检测: 内存越界、悬垂指针、数据竞争、未初始化内存

fn main() {
    // Miri可以检测的错误:
    println!("Miri检测范围:");
    println!("  ✅ 越界内存访问");
    println!("  ✅ 使用已释放内存");
    println!("  ✅ 数据竞争");
    println!("  ✅ 整数溢出(未检查)");
    println!("  ✅ 无效引用");
    
    println!("
安全替代方案优先:");
    println!("  get_unchecked → get()");
    println!("  unsafe零拷贝 → clone()");
    println!("  裸指针 → 引用");
    println!("  unsafe fn → 返回Result");
    
    println!("
标准库中的unsafe用法:");
    println!("  Vec::push     - 可能重新分配");
    println!("  String::from_utf8_unchecked - 跳过验证");
    println!("  slice::from_raw_parts - 裸指针转切片");
}

🏗️ unsafe实战:零拷贝解析器

use std::mem;

// 零拷贝网络包解析
#[repr(C, packed)]
struct IpHeader {
    version_ihl: u8,
    tos: u8,
    total_length: u16,
    identification: u16,
    flags_fragment: u16,
    ttl: u8,
    protocol: u8,
    checksum: u16,
    src_addr: [u8; 4],
    dst_addr: [u8; 4],
}

impl IpHeader {
    fn version(&self) -> u8 { self.version_ihl >> 4 }
    fn header_length(&self) -> u8 { (self.version_ihl & 0x0F) * 4 }
    fn total_length(&self) -> u16 { u16::from_be(self.total_length) }
    fn ttl(&self) -> u8 { self.ttl }
    fn protocol(&self) -> u8 { self.protocol }
    fn src(&self) -> String { format!("{}.{}.{}.{}", self.src_addr[0], self.src_addr[1], self.src_addr[2], self.src_addr[3]) }
    fn dst(&self) -> String { format!("{}.{}.{}.{}", self.dst_addr[0], self.dst_addr[1], self.dst_addr[2], self.dst_addr[3]) }
}

fn parse_ip_packet(data: &[u8]) -> Option<&IpHeader> {
    if data.len() < mem::size_of::() { return None; }
    let ptr = data.as_ptr() as *const IpHeader;
    unsafe {
        let header = &*ptr;
        if header.version() != 4 { return None; }
        Some(header)
    }
}

fn main() {
    let mut packet = vec![0u8; 20];
    packet[0] = 0x45;  // IPv4, 20字节头
    packet[1] = 0x00;  // TOS
    packet[2] = 0x00; packet[3] = 0x28;  // 总长40
    packet[8] = 64;    // TTL
    packet[9] = 6;     // TCP
    packet[12..16].copy_from_slice(&[192, 168, 1, 1]);  // 源地址
    packet[16..20].copy_from_slice(&[10, 0, 0, 1]);     // 目标地址
    
    if let Some(header) = parse_ip_packet(&packet) {
        println!("IP包解析:");
        println!("  版本: IPv{}", header.version());
        println!("  头长度: {}字节", header.header_length());
        println!("  TTL: {}", header.ttl());
        println!("  协议: {} ({})", header.protocol(), if header.protocol()==6{"TCP"}else{"UDP"});
        println!("  源: {}", header.src());
        println!("  目标: {}", header.dst());
    }
}
IP包解析: 版本: IPv4 头长度: 20字节 TTL: 64 协议: 6 (TCP) 源: 192.168.1.1 目标: 10.0.0.1

✅ 验证通过