Rust的多所有权机制

在 Rust 中,通常情况下一个值只能有一个所有者(Owner)。但在实际开发(尤其是 Web 开发)中,我们经常需要多个地方同时持有同一个数据。

比如在 Actix-web 中,你的数据库连接池或全局配置需要被每一个线程、每一个请求处理器(Handler)共享。这时候,单一所有权就不够用了。

为了解决这个问题,Rust 提供了多所有权 机制,核心工具是 Rc 和 Arc。

1. 核心工具:引用计数(Reference Counting)

多所有权的本质不是"复制数据",而是 "共享数据,并记录有多少人在用它"。

Rc<T> (Reference Counted)

  • 适用场景:单线程环境。
  • 原理 :你在堆上存一份数据,每当有人想要所有权,计数器就 + 1 +1 +1;有人不用了,计数器就 − 1 -1 −1。当计数器归零时,数据自动销毁。
  • 缺点:不是线程安全的。

Arc<T> (Atomic Reference Counted)

  • 适用场景 :多线程环境(Actix-web 开发中最常用)。
  • 原理:使用"原子操作"来更新计数器,确保在多线程同时修改计数时不会出错。
  • 代价 :比 Rc 稍微重一点,但在 Web 并发环境下是必须的。

2. 形象的比喻:合租房的钥匙

  • 普通所有权:一间房只有一把钥匙,你要进去,前一个人必须把钥匙彻底给你(Move)。他给你后,他就进不去了。

  • 多所有权 (Arc) :房间还是那间房,但我们可以配很多把钥匙。

    • 每配一把(.clone()),登记簿上的"人数"就 + 1 +1 +1。
    • 每个人离开时把钥匙还回去,人数就 − 1 -1 −1。
    • 最后一个人还钥匙并离开时,灯才会灭,房间(内存)才会被回收。

3. 在 Actix-web 中的实战用法

在 Actix-web 中,你几乎每天都在隐式或显式地使用 Arc。最典型的例子就是 web::Data。

Rust

rust 复制代码
use actix_web::{web, App, HttpServer};
use std::sync::Arc;

struct AppState {
    app_name: String,
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // 1. 创建共享状态
    // web::Data 内部其实就封装了一个 Arc
    let shared_data = web::Data::new(AppState {
        app_name: String::from("MyActixApp"),
    });

    HttpServer::new(move || {
        App::new()
            .app_data(shared_data.clone()) // 2. 这里在为每个线程"配钥匙"
            .route("/", web::get().to(index))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

4. 关键点:多所有权不代表"可变"

这是初学者最容易混淆的地方:Arc 本身只让你"看",不让你"改"。

  • 如果你有多把钥匙进入房间(Arc),你们默认只能进去参观。
  • 如果其中一个人想进去装修(修改数据) ,必须配合内部可变性 工具:Mutex(互斥锁)或 RwLock(读写锁)。

所以,在 Rust 后端开发中,你经常会看到这种"叠罗汉"的写法:Arc<Mutex<T>>

意思是:一个可以多线程共享(Arc)的、能锁住进行修改(Mutex)的数据。

5. 总结

工具 多所有权? 线程安全? 常用场景
Box<T> ❌ 否 ❌ 否 堆上分配数据,单一所有权
Rc<T> ✅ 是 ❌ 否 单线程内的复杂数据共享
Arc<T> ✅ 是 ✅ 是 Web 开发、多线程共享状态、数据库连接池

1). Box<T>:基础的堆内存分配

场景:当你有一个非常大的结构体,不希望在函数调用时在栈 (Stack) 上频繁拷贝;或者你需要定义递归类型。

rust 复制代码
struct BigData {
    data: [u8; 1024 * 1024], // 1MB 的大数据
}

fn main() {
    // 将大数据放入堆(Heap)中,栈上只保留一个指针
    let boxed_data = Box::new(BigData {
        data: [0; 1024 * 1024],
    });

    // 传递时非常快,只移动指针
    process_data(boxed_data);
}

fn process_data(data: Box<BigData>) {
    println!("处理了 {} 字节的数据", data.data.len());
}

2). Rc<T>:单线程内的多处共享

场景:在 GUI 开发或单线程逻辑中,有多个对象需要引用同一个配置,且没有线程安全压力。

rust 复制代码
use std::rc::Rc;

struct Config {
    theme: String,
}

fn main() {
    let common_config = Rc::new(Config {
        theme: "Dark".to_string(),
    });

    // 克隆 Rc,增加计数,不拷贝数据本身
    let component_a = Rc::clone(&common_config);
    let component_b = Rc::clone(&common_config);

    println!("当前配置引用数: {}", Rc::strong_count(&common_config)); // 输出 3
    println!("组件 A 的主题: {}", component_a.theme);
}

3). Arc<T>:Actix-web 中的多线程状态共享

场景:这是你在学习 Actix-web 时最常用的。它让多个 worker 线程能安全地访问同一个全局状态(比如数据库连接、全局计数器)。 注意:如果要修改数据,通常配合 Mutex 使用。

rust 复制代码
use actix_web::{web, App, HttpServer, Responder};
use std::sync::{Arc, Mutex};

// 定义全局状态
struct AppState {
    // Arc 保证多个线程都能持有这个计数器
    // Mutex 保证同一时刻只有一个线程能修改它
    visitor_count: Arc<Mutex<u32>>,
}

async fn index(data: web::Data<AppState>) -> impl Responder {
    // 1. 先锁住数据
    let mut count = data.visitor_count.lock().unwrap();
    // 2. 解引用并修改
    *count += 1;
    
    format!("你是第 {} 位访客", count)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // 创建共享状态
    let app_state = AppState {
        visitor_count: Arc::new(Mutex::new(0)),
    };
    
    // 包装成 Actix 的 Data 类型(它内部也会处理 Arc)
    let data = web::Data::new(app_state);

    HttpServer::new(move || {
        App::new()
            .app_data(data.clone()) // 每个线程都拿到一个 Arc 的副本
            .route("/", web::get().to(index))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

核心区别总结:

  • Box:独占所有权。就像你买了一本书放在家里,只有你能看。

  • Rc:单线程多所有权。就像在家里(单线程),你和爸妈共享一台电视机,看电视的人数为 0 时电视才关掉。

  • Arc:多线程多所有权。就像在公司(多线程),大家共用一台饮水机。因为有多个人(线程)可能同时去接水,所以需要特殊的"原子操作"来记录人数,确保安全。

相关推荐
子兮曰5 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰5 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
爱勇宝5 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
胡写代码5 天前
别再前后端各写一套表单校验了
java·后端
大勇前进5 天前
原生 PHP 还是 Laravel?小项目到底要不要上框架
后端
yuzhi_liu5 天前
我用 LangGraph4j 实现 Multi-Agent Supervisor
后端
alsmile5 天前
Node-RED 之外,国产规则引擎的新方案:基于标准语法,Go 先行实现
后端·开源·go
大白805 天前
PHP 内存溢出排查思路:看懂报错日志,精准定位问题
后端
二月龙5 天前
PHP 接口返回统一响应封装,让前后端对接更省心
后端
盖伦发发5 天前
软件工程SOLID 五大设计原则
后端·软件工程