Rust的多所有权机制

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

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

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

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

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

Rc<T> (Reference Counted)

  • 适用场景:单线程环境。
  • 原理 :你在堆上存一份数据,每当有人想要所有权,计数器就 <math xmlns="http://www.w3.org/1998/Math/MathML"> + 1 +1 </math>+1;有人不用了,计数器就 <math xmlns="http://www.w3.org/1998/Math/MathML"> − 1 -1 </math>−1。当计数器归零时,数据自动销毁。
  • 缺点:不是线程安全的。

Arc<T> (Atomic Reference Counted)

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

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

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

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

    • 每配一把(.clone()),登记簿上的"人数"就 <math xmlns="http://www.w3.org/1998/Math/MathML"> + 1 +1 </math>+1。
    • 每个人离开时把钥匙还回去,人数就 <math xmlns="http://www.w3.org/1998/Math/MathML"> − 1 -1 </math>−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:多线程多所有权。就像在公司(多线程),大家共用一台饮水机。因为有多个人(线程)可能同时去接水,所以需要特殊的"原子操作"来记录人数,确保安全。

相关推荐
踏浪无痕2 小时前
流程引擎、工作流、规则引擎、编排系统、表达式引擎……天呐,我到底该用哪个?
后端·工作流引擎
黄俊懿2 小时前
【深入理解SpringCloud微服务】Gateway源码解析
java·后端·spring·spring cloud·微服务·gateway·架构师
FAQEW2 小时前
若依微服务版(RuoYi-Cloud)本地启动全攻略
前端·后端·微服务·若依·二开
问道飞鱼2 小时前
【Rust编程知识】在 Windows 下搭建完整的 Rust 开发环境
开发语言·windows·后端·rust·开发环境
2501_921649492 小时前
股票 API 对接, 接入德国法兰克福交易所(FWB/Xetra)实现量化分析
后端·python·websocket·金融·区块链
小兔崽子去哪了2 小时前
Java 登录专题
java·spring boot·后端
shark_chili2 小时前
深入剖析arthas技术原理
后端
程序员小假3 小时前
学院本大二混子终于找到实习了...
java·后端
武子康3 小时前
大数据-194 数据挖掘 从红酒分类到机器学习全景:监督/无监督/强化学习、特征空间与过拟合一次讲透
大数据·后端·机器学习