Rust 所有权与内存安全:智能指针、内部可变性与 Unsafe 边界

Rust 内存安全全景深度解析:所有权转移语义、智能指针全解(Box/Rc/Arc/RefCell/Mutex)、内部可变性模式、自定义 Drop trait、内存布局控制、悬垂指针与数据竞争防御、unsafe Rust 的正确使用边界。

目录

  1. 所有权语义深度解析
  2. 智能指针全解
  3. 内部可变性模式
  4. 自定义 Drop 与 RAII
  5. 内存布局与对齐
  6. 悬垂指针与数据竞争防御
  7. Unsafe Rust 安全边界
  8. 性能调优:避免不必要的克隆

1. 所有权语义深度解析

1.1 Move 语义的原理

Rust 的默认行为是 move(转移所有权),而非 copy。对于未实现 Copy trait 的类型,赋值会转移所有权:

let s = String::from("hello");
let t = s;  // s 的所有权 move 到 t
// println!("{}", s); // 编译错误:value borrowed here after move

编译器在 MIR(Middle IR)阶段追踪每个值的所有权状态,确保不会使用已 move 的值。

1.2 Copy trait 的条件

一个类型能实现 Copy 必须满足:

  • 所有字段都实现了 Copy
  • 不包含堆分配(StringVec 等不行)
  • 不包含 Drop trait 实现
#[derive(Copy, Clone)]
struct Point { x: i32, y: i32 } // ✅ 纯栈数据

struct Wrapper(String); // ❌ 无法 Copy

1.3 函数参数的所有权传递

fn takes_ownership(s: String) { /* s 的所有权进入函数 */ }
fn borrows(s: &String) { /* 仅借用 */ }
fn mut_borrows(s: &mut String) { s.push('!'); }

fn main() {
    let s = String::from("hi");
    takes_ownership(s);       // s 被 move
    // takes_ownership(s);    // ❌ 已 move
    
    let mut t = String::from("hello");
    borrows(&t);              // ✅ 不可变借用
    mut_borrows(&mut t);      // ✅ 可变借用
}

2. 智能指针全解

2.1 Box:堆分配

let b = Box::new(5);        // 5 在堆上分配
let list = Box::new(Node {  // 递归结构必须使用 Box
    value: 1,
    next: Some(Box::new(Node { value: 2, next: None })),
});

Box<T> 提供堆分配且保持单所有权,离开作用域时自动释放堆内存。

2.2 Rc:单线程引用计数

use std::rc::Rc;

let data = Rc::new(String::from("shared"));
let d2 = Rc::clone(&data);   // 引用计数 +1
let d3 = Rc::clone(&data);   // 引用计数 +1

println!("count: {}", Rc::strong_count(&data)); // 3
// Rc::downgrade 创建弱引用,打破循环引用

⚠️ Rc<T> 不是线程安全的(未实现 Send + Sync)。

2.3 Arc:原子引用计数(多线程安全)

use std::sync::Arc;
use std::thread;

let data = Arc::new(vec![1, 2, 3]);
let mut handles = vec![];

for i in 0..3 {
    let clone = Arc::clone(&data);
    handles.push(thread::spawn(move || {
        println!("thread {}: {:?}", i, clone);
    }));
}

for h in handles { h.join().unwrap(); }

2.4 RefCell:运行时借用检查

提供「内部可变性」——在不可变引用内部修改数据:

use std::cell::RefCell;

let cell = RefCell::new(5);
*cell.borrow_mut() += 1;     // 运行时检查借用规则
println!("{}", cell.borrow()); // 6

// 违反规则会 panic(运行时,非编译期)
// let r1 = cell.borrow_mut();
// let r2 = cell.borrow_mut(); // thread 'main' panicked

2.5 Mutex 与 RwLock

线程安全的内部可变性:

use std::sync::{Arc, Mutex};

let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];

for _ in 0..10 {
    let c = Arc::clone(&counter);
    handles.push(std::thread::spawn(move || {
        let mut num = c.lock().unwrap();
        *num += 1;
    }));
}

for h in handles { h.join().unwrap(); }
println!("count: {}", *counter.lock().unwrap()); // 10
智能指针组合单线程多线程内部可变性
Rc<RefCell<T>>
Arc<Mutex<T>>
Arc<RwLock<T>>✅(读多写少)

2.6 Cow:写时克隆

use std::borrow::Cow;

fn title_case(input: &str) -> Cow<'_, str> {
    if input.chars().all(|c| !c.is_lowercase()) {
        Cow::Borrowed(input)     // 无需修改,零拷贝
    } else {
        Cow::Owned(input.to_uppercase()) // 需要时才分配
    }
}

3. 内部可变性模式

内部可变性允许通过不可变引用来修改数据。核心实现有三种:

3.1 Cell(仅适用于 Copy 类型)

use std::cell::Cell;

let cell = Cell::new(5);
cell.set(10);              // 不可变引用下修改
println!("{}", cell.get()); // 10

3.2 RefCell(运行时借用检查)

use std::cell::RefCell;

struct Logger {
    logs: RefCell<Vec<String>>,
}

impl Logger {
    fn log(&self, msg: &str) {
        self.logs.borrow_mut().push(msg.to_string()); // &self 下修改
    }
}

3.3 OnceCell / LazyLock(延迟初始化)

Rust 1.80 stabilized std::sync::LazyLock

use std::sync::LazyLock;

static CONFIG: LazyLock<String> = LazyLock::new(|| {
    std::fs::read_to_string("config.toml").unwrap()
});

fn main() {
    println!("{}", *CONFIG); // 首次调用时初始化
}

4. 自定义 Drop 与 RAII

4.1 自定义 Drop trait

struct FileGuard {
    fd: i32,
    path: String,
}

impl Drop for FileGuard {
    fn drop(&mut self) {
        println!("Closing file: {}", self.path);
        // unsafe { libc::close(self.fd); }
    }
}

fn main() {
    let guard = FileGuard { fd: 1, path: "data.txt".to_string() };
    // guard 离开作用域时自动调用 drop()
}

4.2 std::mem::forget 与内存泄漏

let s = String::from("leak");
std::mem::forget(s); // 不调用 drop,产生内存泄漏
// 安全但不好;通常应避免

4.3 ManuallyDrop

use std::mem::ManuallyDrop;

let mut m = ManuallyDrop::new(String::from("manual"));
// 不会自动调用 drop
unsafe { ManuallyDrop::drop(&mut m); } // 显式释放

5. 内存布局与对齐

5.1 查看内存布局

println!("size: {}, align: {}", 
    std::mem::size_of::<String>(),
    std::mem::align_of::<String>()
);

5.2 repr 属性控制布局

#[repr(C)] // C 兼容布局
struct CStruct { a: u8, b: u32 }

#[repr(packed)] // 无填充(紧凑但可能慢)
struct Packed { a: u8, b: u32 }

#[repr(align(16))] // 16 字节对齐
struct Aligned { value: u64 }

5.3 零大小类型(ZST)

struct Unit;           // size = 0
enum Void {}           // size = 0(不可实例化)

let v: Vec<Unit> = vec![Unit; 1000]; // 不分配堆内存!

6. 悬垂指针与数据竞争防御

6.1 编译期防御示例

fn dangling() -> &String {
    let s = String::from("dangle");
    &s // ❌ 编译错误:返回局部变量的引用
}   // s 在这里被 drop

6.2 数据竞争的定义与防御

数据竞争的三个条件(Rust 同时阻止):

  1. 两个或更多指针访问同一数据
  2. 至少一个指针写入数据
  3. 没有同步机制
let mut s = String::from("hello");
let r1 = &s;
let r2 = &mut s; // ❌ 编译错误:不能与不可变借用同时存在
// 规则确保条件 2(写入)且条件 3(无同步)的情况被消除

7. Unsafe Rust 安全边界

Unsafe Rust 允许五项操作:

  1. 解引用裸指针(*const T, *mut T
  2. 调用 unsafe 函数
  3. 实现 unsafe trait
  4. 访问/修改可变静态变量
  5. 使用 union 字段

7.1 安全抽象封装

pub struct MyVec<T> {
    ptr: *mut T,
    len: usize,
    cap: usize,
}

impl<T> MyVec<T> {
    pub fn get(&self, index: usize) -> Option<&T> {
        if index >= self.len { return None; }
        // unsafe 块最小化
        Some(unsafe { &*self.ptr.add(index) })
    }
}

原则:unsafe 块应该尽量小,且被安全的 API 封装。调用者无需关心内部 unsafe。

7.2 裸指针 vs 引用

let mut x = 5;
let r = &mut x as *mut i32;       // 从引用创建裸指针
unsafe {
    *r = 10;                       // 解引用裸指针
    println!("{}", *r);
}

裸指针所有权由程序员管理,无生命周期检查。

7.3 Unsafe 最佳实践

/// # Safety
/// 调用者必须确保 ptr 指向有效的、已初始化的 T 值
/// 且 ptr 的生命周期至少与返回引用一样长
unsafe fn from_raw<'a, T>(ptr: *const T) -> &'a T {
    &*ptr
}

8. 性能调优:避免不必要的克隆

8.1 使用引用代替克隆

// ❌ 低效
fn process(data: Vec<String>) -> Vec<String> { ... }

// ✅ 高效:借用
fn process(data: &[String]) -> Vec<String> { ... }

8.2 Cow<’_, str> 策略模式

use std::borrow::Cow;

fn normalize(input: &str) -> Cow<'_, str> {
    if input.starts_with("prefix_") {
        Cow::Borrowed(input)
    } else {
        Cow::Owned(format!("prefix_{}", input))
    }
}

8.3 使用 &str 而非 String 接收参数

// ❌ 强制调用者 clone
fn greet(name: String) { println!("Hello, {}", name); }

// ✅ 灵活接受 String 和 &str
fn greet(name: &str) { println!("Hello, {}", name); }

greet("world");              // &str
greet(&String::from("world")); // &String 自动解引用

掌握 Rust 的所有权与内存安全系统后,可以写出无需 GC 但保证无内存泄漏、无数据竞争的高性能代码。这是 Rust 区别于其他系统语言的核心竞争力。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「rust」更多文章