正在加载,请稍候…

Rust 所有权实战:无惧构建内存安全系统

深入理解 Rust 所有权、借用、生命周期及智能指针,掌握内存安全并发编程,避免常见陷阱,构建可靠系统。

Rust 所有权实战:无惧构建内存安全系统

Rust 所有权实战:无惧构建内存安全系统

Rust 的所有权系统是该语言的标志性特性——一种编译时机制,消除了包括空指针解引用、悬垂指针、数据竞争和释放后使用在内的整类错误。经过多年的生产使用,已经涌现出一些模式,使得与借用检查器协作变得自然而非令人沮丧。

所有权的三条规则

Rust 的所有权系统建立在三条基本规则之上:

  1. Rust 中的每个值都有一个唯一的所有者。
  2. 同一时间只能有一个所有者。
  3. 当所有者离开作用域时,该值将被丢弃。
fn main() {
    let s1 = String::from("hello"); // s1 拥有该 String
    let s2 = s1;                    // 所有权 移动 到 s2
    // println!("{}", s1);          // 错误:s1 已被移动
    println!("{}", s2);             // 正确
} // s2 离开作用域,String 被丢弃

Rust 所有权实战:无惧构建内存安全系统 插图

借用和引用

借用允许你在不获取所有权的情况下使用值:

fn calculate_length(s: &String) -> usize {
    s.len()
}

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);
    println!("Length of '{}' is {}", s1, len); // s1 仍然有效
}

可变引用允许修改,但有约束:

fn change(s: &mut String) {
    s.push_str(", world");
}

fn main() {
    let mut s = String::from("hello");
    change(&mut s);
    println!("{}", s); // "hello, world"
}

借用检查器在编译时强制执行这些规则:

fn main() {
    let mut s = String::from("hello");

    let r1 = &s;     // 正确:不可变借用
    let r2 = &s;     // 正确:允许多个不可变借用
    // let r3 = &mut s; // 错误:存在不可变借用时

    println!("{} and {}", r1, r2); // r1, r2 最后使用处

    let r3 = &mut s; // 现在正确
    println!("{}", r3);
}

理解生命周期

生命周期确保引用不会超过它们所指向的数据的存活时间:

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

fn main() {
    let s1 = String::from("long string");
    let result;
    {
        let s2 = String::from("xyz");
        result = longest(s1.as_str(), s2.as_str());
        println!("Longest: {}", result);
    }
}

结构体中的生命周期标注:

struct ImportantExcerpt<'a> {
    part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention: {}", announcement);
        self.part
    }
}

选择 Box、Rc 和 Arc

Box:堆上的单一所有权

#[derive(Debug)]
enum List {
    Cons(i32, Box<List>),
    Nil,
}

fn main() {
    let list = List::Cons(1,
        Box::new(List::Cons(2,
            Box::new(List::Cons(3,
                Box::new(List::Nil))))));
    println!("{:?}", list);
}

Rust 所有权实战:无惧构建内存安全系统 插图

Rc:多重所有权(单线程)

use std::rc::Rc;

fn main() {
    let a = Rc::new(vec![1, 2, 3]);
    let b = Rc::clone(&a);
    let c = Rc::clone(&a);

    println!("Reference count: {}", Rc::strong_count(&a)); // 3
    drop(c);
    println!("After drop: {}", Rc::strong_count(&a)); // 2
}

Arc:跨线程的多重所有权

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

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

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

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

    println!("Result: {}", *counter.lock().unwrap()); // 10
}

内部可变性:RefCell 和 Mutex

有时你需要在借用检查器不允许的情况下获得可变性:

use std::cell::RefCell;

#[derive(Debug)]
struct Node {
    value: i32,
    children: RefCell<Vec<Node>>,
}

impl Node {
    fn new(value: i32) -> Self {
        Node { value, children: RefCell::new(vec![]) }
    }

    fn add_child(&self, child: Node) {
        self.children.borrow_mut().push(child);
    }
}

内存安全并发

Rust 的所有权系统在编译时使数据竞争变得不可能:

use std::sync::{Arc, RwLock};
use std::thread;
use std::collections::HashMap;

type SharedCache = Arc<RwLock<HashMap<String, String>>>;

async fn cache_put(cache: SharedCache, key: String, value: String) {
    let mut w = cache.write().unwrap();
    w.insert(key, value);
}

async fn cache_get(cache: SharedCache, key: &str) -> Option<String> {
    let r = cache.read().unwrap();
    r.get(key).cloned()
}

Rust 所有权实战:无惧构建内存安全系统 插图

常见陷阱及如何避免

避免过度使用 Clone

// 糟糕:不必要地克隆
fn process_names_bad(names: Vec<String>) -> Vec<String> {
    names.iter()
        .map(|n| n.clone().to_uppercase())
        .collect()
}

// 良好:使用引用
fn process_names(names: &[String]) -> Vec<String> {
    names.iter()
        .map(|n| n.to_uppercase())
        .collect()
}

丢弃顺序很重要

use std::sync::Mutex;

struct Foo { mutex: Mutex<i32> }

fn safe() {
    let foo = Foo { mutex: Mutex::new(0) };
    {
        let guard = foo.mutex.lock().unwrap();
        // 使用 guard
    } // guard 在此处丢弃,互斥锁解锁
    drop(foo); // 现在可以安全丢弃
}

实用模式:带所有权的构建器

#[derive(Debug)]
struct Config {
    host: String,
    port: u16,
    max_connections: usize,
}

struct ConfigBuilder {
    host: String,
    port: u16,
    max_connections: usize,
}

impl ConfigBuilder {
    fn new() -> Self {
        ConfigBuilder {
            host: "localhost".to_string(),
            port: 8080,
            max_connections: 100,
        }
    }

    fn host(mut self, host: impl Into<String>) -> Self {
        self.host = host.into();
        self
    }

    fn port(mut self, port: u16) -> Self {
        self.port = port;
        self
    }

    fn build(self) -> Config {
        Config {
            host: self.host,
            port: self.port,
            max_connections: self.max_connections,
        }
    }
}

fn main() {
    let config = ConfigBuilder::new()
        .host("production.example.com")
        .port(443)
        .build();
    println!("{:?}", config);
}

结论

Rust 的所有权系统不是限制——而是一种超能力。通过在类型系统中编码内存安全规则,Rust 消除了困扰 C 和 C++ 程序的整类错误。初期的学习曲线会在生产可靠性上得到回报。从简单的拥有类型开始,在性能关键时使用引用,跨线程共享时使用 Arc/Mutex。通过练习,借用检查器将成为值得信赖的协作者而非对手。