🦀 Rust 复杂架构设计

当项目从 1 万行膨胀到 100 万行,Rust 的类型系统和所有权模型从约束变成了武器。这篇文章讲的是:如何在 Rust 的规则下,设计真正可演进的复杂系统。

所有权架构 类型驱动设计 Actor模型 ECS 编译期保证 零成本抽象

📑 目录

  1. Rust 架构的底层哲学:所有权即架构
  2. 类型驱动设计:让编译器当架构师
  3. 模块系统与 Crate 拓扑
  4. 并发架构:从 Mutex 到 Actor
  5. ECS 架构:数据导向的 Rust 方式
  6. 错误处理架构:类型即文档
  7. 插件系统与动态扩展
  8. 大型项目的编译与迭代策略
  9. 实战:设计一个分布式任务调度器
  10. 反模式与常见陷阱

1. Rust 架构的底层哲学:所有权即架构

在 Go/Java/Python 里,架构师画完架构图,开发者用引用和回调把东西连起来——一切都很灵活。Rust 不一样:所有权关系就是你的架构图。谁能访问什么数据、谁能修改什么状态,不是靠文档约束,而是靠编译器强制。

1.1 所有权拓扑决定系统拓扑

一个 Rust 程序的所有权图,本质上是一棵有向无环树。每个值有且仅有一个 owner,借用只是临时访问权。这意味着:

┌─────────────────────────────────────────────────┐ │ App (owns everything) │ │ ├── Config (owned by App) │ │ ├── StateManager (owned by App) │ │ │ ├── ConnectionPool (owned by StateManager) │ │ │ └── CacheLayer (owned by StateManager) │ │ ├── TaskScheduler (owned by App) │ │ │ └── WorkerPool (owned by TaskScheduler) │ │ └── MetricsCollector (owned by App) │ └─────────────────────────────────────────────────┘ Rule: 父节点 owns 子节点,子节点可 borrow 父节点数据 Effect: 数据流方向 = 所有权方向,反向访问 = 显式借用

🔑 核心原则:在设计 Rust 系统时,先画所有权树,再写代码。如果你的所有权图是网状的(循环引用、多重拥有),说明架构有问题,需要引入间接层(Arc、索引、消息传递)来打破循环。

1.2 四种"共享可变状态"的解法

Rust 的 aliasing + mutation 规则禁止同时有可变引用和不可变引用。但在复杂系统中,共享可变状态是刚需。Rust 给了你四条路:

方案适用场景复杂度运行时开销典型模式
RefCell<T>单线程,需要运行时借用检查极小(borrow counter)树形结构内部可变
Mutex<T> / RwLock<T>多线程,需要互斥访问锁竞争共享配置、全局状态
Arc<Mutex<T>>多线程 + 多 owner原子计数 + 锁跨组件共享资源
消息传递 mpsc / crossbeam多线程,无需共享内存Channel 开销Actor 模型、管道

✅ 好的设计:所有权清晰

struct App {
    config: Config,        // App owns
    db: Database,          // App owns
    workers: Vec<Worker>,  // App owns
}

impl App {
    fn process(&mut self, req: Request) {
        // 借用链清晰:&mut self → &mut db
        self.db.save(req.data());
        self.workers[0].notify();
    }
}

❌ 坏的设计:Arc 地狱

struct App {
    db: Arc<Mutex<Database>>,     // 谁都能 lock
    cache: Arc<RwLock<Cache>>,     // 随时可能死锁
    workers: Arc<Mutex<Vec<Worker>>>, // 为什么要 Arc?
    state: Arc<Mutex<Arc<Mutex<State>>>>, // 😱
}

// 没人知道谁在什么时候持有锁
// 没人能证明不会死锁
// 编译器帮不了你

⚠️ Arc<Mutex<T>> 闻到味道:如果你发现代码里到处都是 Arc<Mutex<T>>,这通常意味着架构缺少清晰的 ownership 边界。每多一层 Arc<Mutex>,编译器能帮你检查的东西就少一层。正确做法:先重构所有权结构,让大多数数据只有一个 owner,只在真正的跨 owner 共享点用 Arc。

2. 类型驱动设计:让编译器当架构师

Rust 最强大的架构工具不是 trait,不是宏,而是类型系统本身。在 Rust 里,一个好的设计是:非法状态不可表达(make illegal states unrepresentable)。

2.1 用 enum 建模状态机

Rust
/// 连接状态机:编译器保证你不会在 Disconnected 状态下发送数据
enum ConnectionState {
    Disconnected,
    Connecting(TcpStream, Instant),  // stream + 超时时间
    Connected {
        stream: TcpStream,
        last_heartbeat: Instant,
        seq: u64,
    },
    Reconnecting {
        attempts: u8,
        backoff: Duration,
    },
}

struct Connection {
    state: ConnectionState,
}

impl Connection {
    fn send(&mut self, data: &[u8]) -> Result<(), ConnError> {
        match &mut self.state {
            ConnectionState::Connected { stream, seq, .. } => {
                *seq += 1;
                stream.write_all(data)?;
                Ok(())
            }
            // 编译器强制你处理每个状态
            ConnectionState::Disconnected => Err(ConnError::NotConnected),
            ConnectionState::Connecting(_, _) => Err(ConnError::InProgress),
            ConnectionState::Reconnecting { attempts, .. } => {
                Err(ConnError::Reconnecting { attempts: *attempts })
            }
        }
    }
}

对比 Java 的做法:一个 Connection 类,里面有 boolean connectedboolean connecting,运行时靠 if-else 判断状态——你永远无法在编译期阻止别人在 disconnected 时调用 send。

2.2 用 newtype 表达领域概念

Rust
// 不要用裸类型——编译器无法区分 UserId 和 OrderId
// struct User { id: u64 }     // ❌ 容易和 Order.id 混淆

// 用 newtype 让编译器帮你守门
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct UserId(u64);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct OrderId(u64);

fn get_user(id: UserId) -> User { ... }
fn get_order(id: OrderId) -> Order { ... }

// 现在这样写会编译错误:
// get_user(OrderId(42))  // ❌ type mismatch!
// 只有 get_user(UserId(42)) 才能编译通过

2.3 PhantomData 和类型状态模式

类型状态模式(Type State Pattern)是 Rust 独有的设计模式:用泛型参数编码对象的状态,让状态转换在编译期完成检查。

Rust
use std::marker::PhantomData;

// 状态编码在类型参数里
struct Unconfigured;
struct Configured;
struct Running;

struct Server<State = Unconfigured> {
    config: Option<ServerConfig>,
    listener: Option<TcpListener>,
    _state: PhantomData<State>,
}

impl Server<Unconfigured> {
    fn new() -> Self { ... }
    
    // configure 消费 Unconfigured,返回 Configured
    fn configure(self, cfg: ServerConfig) -> Server<Configured> {
        Server {
            config: Some(cfg),
            listener: None,
            _state: PhantomData,
        }
    }
}

impl Server<Configured> {
    // start 只在 Configured 状态可用
    fn start(self) -> Result<Server<Running>, Error> {
        let config = self.config.unwrap(); // 编译器保证 Some
        let listener = TcpListener::bind(config.addr)?;
        Ok(Server {
            config: Some(config),
            listener: Some(listener),
            _state: PhantomData,
        })
    }
}

impl Server<Running> {
    fn accept(&self) -> Result<TcpStream, Error> { ... }
}

// 使用:
let server = Server::new()
    .configure(config)       // 必须先 configure
    .start()?;               // 才能 start
// server.accept()           // 才能 accept

// 你无法在 Unconfigured 状态调用 start()——编译错误!
// 你无法跳过 configure 直接 start——编译错误!

✅ 零成本:PhantomData 不占运行时内存,状态转换在编译期完成,运行时零开销。这比任何运行时状态检查都快,也比任何文档都可靠。

3. 模块系统与 Crate 拓扑

3.1 模块 ≠ 命名空间

Rust 的 mod 不仅仅是代码组织,它定义了可见性边界pub(crate)pub(super) 让你能精确控制谁能看到什么——这在大型项目中是架构级别的控制。

crate: my-app ├── config/ pub(crate) — 内部共享配置 │ ├── mod.rs pub fn load() → Config │ └── validator.rs pub(super) fn validate() — 只给 config 内部用 ├── db/ pub — 对外暴露查询接口 │ ├── mod.rs pub fn query() │ ├── pool.rs pub(crate) struct Pool — 内部实现 │ └── migration.rs pub(crate) fn run_migrations() ├── service/ pub — 业务逻辑 │ ├── mod.rs │ ├── user.rs pub fn create_user() │ └── order.rs pub fn place_order() └── infra/ pub(crate) — 不对外暴露 ├── metrics.rs ├── logging.rs └── retry.rs

3.2 Crate 拆分的黄金法则

原则描述反例
依赖方向单向Crate A 依赖 B,B 不能依赖 A循环依赖 → 合并或抽接口 crate
核心 crate 无外部依赖数据类型和 trait 定义不依赖 tokio/serdedomain crate 依赖 async runtime
编译时间隔离频繁改动的代码和稳定代码分开所有代码在一个 crate 里
可选依赖向下只有叶 crate 可以有 optional deps核心 crate 有 20 个 feature flags

3.3 推荐 Crate 拓扑(中型项目)

┌─────────────┐ │ my-app-bin │ (binary, thin wrapper) └──────┬──────┘ │ depends on ┌────────────┼────────────┐ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ my-app-api │ │ my-app-db │ │ my-app-infra│ │ HTTP/gRPC│ │ Postgres │ │ Metrics │ │ handlers │ │ queries │ │ Logging │ └─────┬────┘ └─────┬────┘ └─────┬────┘ │ │ │ └─────────────┼─────────────┘ ▼ ┌─────────────┐ │ my-app-core │ (零外部依赖!) │ Types │ │ Traits │ │ Domain logic│ └─────────────┘

🔑 my-app-core 的价值:它只定义 trait 和数据类型,不依赖 tokio/serde/任何 async runtime。这意味着:
① 单元测试可以纯同步跑,快 10 倍
② 替换 HTTP 框架不影响业务逻辑
③ 可以在 WASM/embedded 环境复用核心逻辑

4. 并发架构:从 Mutex 到 Actor

4.1 Rust 并发的五层模型

Layer 5: Actor / CSP tokio-actor, actix, ractor ───────────────────────────────────────────────── Layer 4: Structured Concurrency tokio::spawn + JoinSet +Cancellation ───────────────────────────────────────────────── Layer 3: Async Primitives channels, watch, semaphore, barrier ───────────────────────────────────────────────── Layer 2: Sync Primitives Mutex, RwLock, Condvar, OnceLock ───────────────────────────────────────────────── Layer 1: Send + Sync 编译器强制的线程安全保证

4.2 选择决策树

需要共享数据吗? ├── 否 → 各自拥有,消息传递 (channel) └── 是 → 多线程同时写吗? ├── 否 → RwLock (读多写少) └── 是 → 数据之间有依赖吗? ├── 否 → 分片 Mutex (sharded lock) └── 是 → 数据结构复杂吗? ├── 简单 → Mutex + 明确锁序 └── 复杂 → Actor 模型 (内部单线程, 外部消息)

4.3 Actor 模型的 Rust 实现

Actor 模型天然契合 Rust 的所有权理念:每个 Actor 独占自己的状态,外部通过消息通信,没有共享可变状态。

Rust — 用 tokio channel 实现轻量 Actor
use tokio::sync::{mpsc, oneshot};

// Actor 的消息协议——用 enum 强类型化
enum DbActorMsg {
    Get { key: String, reply: oneshot::Sender<Option<String>> },
    Set { key: String, value: String },
    Delete { key: String },
    Flush { reply: oneshot::Sender<()> },
}

struct DbActor {
    rx: mpsc::Receiver<DbActorMsg>,
    data: HashMap<String, String>,     // Actor 独占,无需锁
    pending_writes: Vec<String>,
}

impl DbActor {
    async fn run(&mut self) {
        while let Some(msg) = self.rx.recv().await {
            match msg {
                DbActorMsg::Get { key, reply } => {
                    let _ = reply.send(self.data.get(&key).cloned());
                }
                DbActorMsg::Set { key, value } => {
                    self.data.insert(key.clone(), value);
                    self.pending_writes.push(key);
                }
                DbActorMsg::Delete { key } => {
                    self.data.remove(&key);
                }
                DbActorMsg::Flush { reply } => {
                    self.flush_to_disk().await;
                    self.pending_writes.clear();
                    let _ = reply.send(());
                }
            }
        }
    }
}

// Actor 的 handle——对外暴露的类型安全接口
struct DbHandle {
    tx: mpsc::Sender<DbActorMsg>,
}

impl DbHandle {
    pub async fn get(&self, key: String) -> Option<String> {
        let (tx, rx) = oneshot::channel();
        self.tx.send(DbActorMsg::Get { key, reply: tx }).await.ok()?;
        rx.await.ok()?
    }
    
    pub async fn set(&self, key: String, value: String) {
        self.tx.send(DbActorMsg::Set { key, value }).await.ok();
    }
}

✅ 为什么 Actor 在 Rust 里特别香
① 状态不需要锁——Actor 内部单线程执行
② 消息协议是 enum——编译器保证你处理了所有消息类型
oneshot::Sender 做请求-响应——类型安全,不会搞混 reply
④ Actor 崩溃不影响其他 Actor——天然容错边界

5. ECS 架构:数据导向的 Rust 方式

Entity-Component-System 是游戏引擎的标准架构,但它的思想——组合优于继承,数据与逻辑分离——同样适用于任何需要高性能、高扩展性的 Rust 项目。

5.1 为什么 OOP 在 Rust 里别扭

✅ ECS 思路

// Entity = ID
// Component = 纯数据
struct Position(f32, f32);
struct Velocity(f32, f32);
struct Health(i32);

// System = 纯逻辑
fn movement_sys(
    pos: &mut Position,
    vel: &Velocity,
) {
    pos.0 += vel.0;
    pos.1 += vel.1;
}

// 数据连续存储,CPU cache 友好
// 逻辑无状态,天然并行

❌ OOP 思路

// trait GameObject {
//     fn update(&mut self);
//     fn render(&self);
// }
// 
// struct Player { ... }
// impl GameObject for Player { ... }
//
// struct Enemy { ... }  
// impl GameObject for Enemy { ... }
//
// 问题1:异构集合 Vec<Box<dyn GameObject>>
//        → 虚函数调用 + cache miss
// 问题2:新功能需改所有 struct
// 问题3:无法组合 (会飞+会游泳?)

5.2 用 Rust 实现 Mini ECS

Rust
use std::any::{Any, TypeId};
use std::collections::HashMap;

type EntityId = u64;

struct World {
    entities: Vec<EntityId>,
    components: HashMap<TypeId, Box<dyn Any>>,  // TypeId → Vec<Component>
    next_id: EntityId,
}

impl World {
    fn spawn(&mut self) -> EntityId {
        let id = self.next_id;
        self.next_id += 1;
        self.entities.push(id);
        id
    }
    
    fn add_component<T: 'static>(&mut self, entity: EntityId, comp: T) {
        let type_id = TypeId::of::<T>();
        let storage = self.components
            .entry(type_id)
            .or_insert_with(|| Box::new(Vec::<(EntityId, T)>::new()));
        let vec = storage.downcast_mut::<Vec<(EntityId, T)>>().unwrap();
        vec.push((entity, comp));
    }
    
    fn query<T: 'static>(&self) -> Vec<(EntityId, &T)> {
        let type_id = TypeId::of::<T>();
        self.components
            .get(&type_id)
            .and_then(|s| s.downcast_ref::<Vec<(EntityId, T)>>())
            .map(|v| v.iter().map(|(id, c)| (*id, c)).collect())
            .unwrap_or_default()
    }
    
    fn query2<A: 'static, B: 'static>(&self) -> Vec<(EntityId, &A, &B)> {
        // Join two component storages on EntityId
        let a_map: HashMap<_, _> = self.query::<A>().into_iter().collect();
        let b_map: HashMap<_, _> = self.query::<B>().into_iter().collect();
        a_map.keys()
            .filter_map(|&id| {
                Some((id, a_map.get(&id)?, b_map.get(&id)?))
            })
            .collect()
    }
}

// 使用
let mut world = World::new();
let player = world.spawn();
world.add_component(player, Position(0.0, 0.0));
world.add_component(player, Velocity(1.0, 0.0));
world.add_component(player, Health(100));

// Movement system: 只关心有 Position + Velocity 的 entity
for (id, pos, vel) in world.query2::<Position, Velocity>() {
    println!("Entity {} at ({}, {}) moving ({}, {})", 
             id, pos.0, pos.1, vel.0, vel.1);
}

🔑 生产级 ECS:实际项目用 bevy_ecs(可独立使用,不依赖 bevy 引擎)或 hecs。它们提供了 archetype-based 存储、并行调度、change detection 等生产级功能。

6. 错误处理架构:类型即文档

6.1 错误分层的金字塔

┌─────────────────────────┐ │ Fatal: 进程级崩溃 │ panic! / unwrap │ 内存耗尽、栈溢出 │ 不可恢复 ├─────────────────────────┤ │ Error: 操作级失败 │ thiserror / anyhow │ IO失败、网络超时 │ 可恢复,需传播 ├─────────────────────────┤ │ Warn: 预期的异常情况 │ tracing::warn! │ 重试成功、降级响应 │ 继续执行 ├─────────────────────────┤ │ Ok: 正常返回 │ Result::Ok │ 业务逻辑正常完成 │ Happy path └─────────────────────────┘

6.2 错误类型设计:domain error > anyhow

Rust — 用 thiserror 定义领域错误
use thiserror::Error;

#[derive(Error, Debug)]
enum AppError {
    #[error("Database error: {0}")]
    Database(#[from] DbError),
    
    #[error("User {user_id} not found")]
    UserNotFound { user_id: UserId },
    
    #[error("Rate limited: {requests} requests in {window:?}")]
    RateLimited { requests: u32, window: Duration },
    
    #[error("Invalid configuration: {message}")]
    Config { message: String },
    
    #[error(transparent)]
    Internal(#[from] anyhow::Error),  // 兜底
}

// 每个错误变体携带足够的上下文用于调试
// #[from] 自动实现 From,? 运算符自动转换

6.3 错误传播的架构规则

错误类型处理策略
Crate 内部thiserror 定义具体 enum? 传播,不丢弃上下文
Crate 边界转换为本 crate 的错误类型From + ?,隐藏内部细节
应用顶层anyhow::Error收集所有错误,统一日志/上报
API 边界HTTP 状态码 + JSON错误 → 状态码映射,不泄露内部信息

7. 插件系统与动态扩展

7.1 三种扩展机制对比

机制编译期/运行期类型安全性能典型用例
Trait 对象 dyn T运行期虚函数调用策略模式、插件接口
泛型 impl T编译期单态化,零开销算法、容器
过程宏编译期编译时开销代码生成、DSL
WASM 插件运行期沙箱开销不可信插件
dylib 动态加载运行期C FFI 开销热加载、第三方扩展

7.2 用 trait 设计插件接口

Rust
// 核心 crate 定义插件接口
pub trait Plugin: Send + Sync {
    fn name(&self) -> &str;
    fn on_load(&self, ctx: &PluginContext) -> Result<(), PluginError>;
    fn on_request(&self, req: &mut Request) -> Result<(), PluginError>;
    fn on_response(&self, resp: &mut Response) -> Result<(), PluginError>;
    fn on_unload(&self) {}
}

pub struct PluginContext {
    pub config: &Config,
    pub metrics: &MetricsSink,
}

// 插件管理器
struct PluginManager {
    plugins: Vec<Box<dyn Plugin>>,   // 运行期多态
}

impl PluginManager {
    fn register(&mut self, plugin: Box<dyn Plugin>) {
        self.plugins.push(plugin);
    }
    
    fn on_request(&self, req: &mut Request) -> Result<(), PluginError> {
        for plugin in &self.plugins {
            plugin.on_request(req)?;  // 责任链模式
        }
        Ok(())
    }
}

// 具体插件——可以独立 crate
struct AuthPlugin { secret: String }
impl Plugin for AuthPlugin {
    fn name(&self) -> &str { "auth" }
    fn on_request(&self, req: &mut Request) -> Result<(), PluginError> {
        let token = req.header("Authorization")
            .ok_or(PluginError::Unauthorized)?;
        self.verify(token)?;
        Ok(())
    }
    // ...
}

8. 大型项目的编译与迭代策略

10x
模块拆分后增量编译提速
30s→3s
改一行代码的重新编译时间
0
核心 crate 的外部依赖数

8.1 编译时间优化清单

策略效果实现
Crate 拆分★★★频繁改动的代码独立成 crate
减少泛型单态化★★★热点路径用 dyn,冷路径用泛型
依赖裁剪★★cargo tree -d 去重,feature 精简
cfg(test) 隔离★★测试依赖不进入生产编译
sccache / mold缓存和更快的 linker

8.2 CI 策略:分层编译

Stage 1: core crate (30s) ├── cargo check -p my-app-core └── cargo test -p my-app-core Stage 2: all crates check (2min) ├── cargo check --workspace └── cargo clippy --workspace Stage 3: test + build (5min) ├── cargo test --workspace └── cargo build --release Stage 4: integration test (10min) ├── docker compose up -d └── cargo test --test integration

9. 实战:设计一个分布式任务调度器

把前面所有概念串起来,设计一个分布式任务调度器(类似简化版 Temporal / Nomad)。

9.1 架构总览

┌──────────────┐ │ Client SDK │ │ submit(job) │ └──────┬───────┘ │ gRPC ┌──────▼───────┐ │ API Server │ │ (stateless) │ └──────┬───────┘ │ ┌─────────────┼─────────────┐ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │Scheduler │ │Scheduler │ │Scheduler │ (Raft 选举 leader) │ Leader │ │ Follower │ │ Follower │ └─────┬────┘ └──────────┘ └──────────┘ │ assigns tasks ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Worker │ │ Worker │ │ Worker │ │ (Actor) │ │ (Actor) │ │ (Actor) │ └─────┬────┘ └─────┬────┘ └─────┬────┘ │ │ │ └─────────────┼─────────────┘ ▼ ┌──────────────┐ │ Storage │ │ (Event Sourced)│ └──────────────┘

9.2 核心类型设计

Rust — core crate (零外部依赖)
// === 领域类型 ===
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct JobId(Uuid);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct WorkerId(Uuid);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct TaskId(Uuid);

// === 状态机 ===
enum JobState {
    Pending,
    Scheduled { worker: WorkerId, task: TaskId },
    Running { worker: WorkerId, started_at: Instant },
    Completed { result: JobResult },
    Failed { error: String, retries: u8 },
    Cancelled,
}

enum TaskState {
    Queued,
    Dispatched { worker: WorkerId },
    Running { heartbeat: Instant },
    Finished { exit_code: i32 },
    Crashed { signal: i32 },
    Lost,  // worker 失联
}

// === 事件溯源 ===
enum JobEvent {
    Created { spec: JobSpec },
    Scheduled { worker: WorkerId, task: TaskId },
    Started { worker: WorkerId, at: Timestamp },
    Heartbeat { worker: WorkerId, progress: f32 },
    Completed { result: JobResult },
    Failed { error: String },
    RetryScheduled { attempt: u8, after: Duration },
    Cancelled { reason: String },
}

// === 插件接口 ===
trait SchedulerPlugin: Send + Sync {
    fn on_job_created(&self, job: &Job) -> Option<ScheduleHint>;
    fn on_task_finished(&self, result: &TaskResult) -> Vec<Action>;
    fn on_worker_lost(&self, worker: WorkerId, tasks: &[TaskId]) -> Vec<Action>;
}

enum Action {
    Reschedule { task: TaskId, priority: Priority },
    Notify { channel: String, message: String },
    ScaleUp { count: u32 },
}

enum ScheduleHint {
    Prefer { worker: WorkerId },
    Avoid { worker: WorkerId },
    Require { resource: String, amount: u64 },
}

9.3 Worker Actor 实现

Rust — Worker 用 Actor 模型
enum WorkerMsg {
    Dispatch { task: Task, reply: oneshot::Sender<Result<(), DispatchError>> },
    Cancel { task_id: TaskId },
    HeartbeatCheck,
    Shutdown { reply: oneshot::Sender<()> },
}

struct WorkerActor {
    id: WorkerId,
    capacity: u32,
    running: HashMap<TaskId, JoinHandle<()>>,
    rx: mpsc::Receiver<WorkerMsg>,
    heartbeat_tx: watch::Sender<WorkerStatus>,
}

impl WorkerActor {
    async fn run(&mut self) {
        let mut heartbeat_interval = tokio::time::interval(Duration::from_secs(5));
        
        loop {
            tokio::select! {
                Some(msg) = self.rx.recv() => {
                    match msg {
                        WorkerMsg::Dispatch { task, reply } => {
                            let handle = self.spawn_task(task).await;
                            let _ = reply.send(handle);
                        }
                        WorkerMsg::Cancel { task_id } => {
                            if let Some(h) = self.running.remove(&task_id) {
                                h.abort();
                            }
                        }
                        WorkerMsg::Shutdown { reply } => {
                            for (_, h) in self.running.drain() { h.abort(); }
                            let _ = reply.send(());
                            return;
                        }
                        WorkerMsg::HeartbeatCheck => {
                            self.cleanup_finished();
                        }
                    }
                }
                _ = heartbeat_interval.tick() => {
                    let status = WorkerStatus {
                        id: self.id,
                        running: self.running.len() as u32,
                        capacity: self.capacity,
                    };
                    let _ = self.heartbeat_tx.send(status);
                }
            }
        }
    }
}

10. 反模式与常见陷阱

🚫 Arc<Mutex<Everything>>

所有字段都包 Arc<Mutex> → 编译器无法验证正确性 → 运行时死锁/竞态。
解法:重新设计所有权,让数据有单一 owner,用 channel 替代共享。

🚫 God Struct

一个 struct 持有所有状态,所有方法都 &mut self → 无法并行。
解法:拆成多个独立 struct,各自持有自己的数据。

🚫 Stringly Typed

到处传 String,用字符串匹配做逻辑 → 运行时才知道出错。
解法:newtype + enum,让编译器替你检查。

🚫 Callback Hell

大量闭包回调 + 生命周期参数爆炸 → 代码不可读。
解法:用 channel/future 替代回调,或用 Actor 模型。

🚫 过度抽象

5 层 trait 嵌套,泛型参数满天飞 → 编译慢 + 错误信息不可读。
解法:YAGNI。只在有第二种实现时才抽 trait。

🚫 .clone() 万岁

到处 .clone() 绕过借用检查 → 性能崩盘。
解法:先理解为什么借用检查失败,重构数据流,而不是无脑 clone。

🔴 终极测试:如果你的 Rust 项目要加 unsafe 才能完成一个常规业务功能,大概率是架构有问题,不是语言有问题。回去重新设计所有权和类型。Rust 编译器的拒绝是你的朋友——它在告诉你设计有缺陷。

🦀 Rust 架构的终极原则
让编译器做架构审查,而不是代码审查。
让类型表达业务规则,而不是注释。
让所有权约束数据流,而不是文档。
让非法状态不可编译,而不是运行时报错。

当你的 Rust 代码能编译通过时,它不仅是类型正确的——它的架构也是正确的。