You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rocket.rs托管状态更新报错:无法修改rocket::State<Timer>解引用数据

在Rocket请求处理器中修改托管状态的解决方案

问题场景

作为Rust新手,尝试在Rocket的请求处理器中更新托管状态时遇到编译错误,代码示例如下:

#[derive(Debug, Serialize, Deserialize)]
struct Timer {
    playing: bool,
    paused: bool,
    start_at: AtomicUsize,
}

#[post("/message", data = "<form>")]
fn message(form: Form<Message>, timer: &State<Timer>) {
    timer.start_at.store(time, Ordering::Relaxed); // 这行没问题
    timer.playing = true; // 此处报错
    timer.paused = true; // 此处也报错
}

// 启动Rocket
...
let _rocket = rocket::build()
        .manage(Timer {
            start_at: AtomicUsize::new(0),
            playing: false,
            paused: false,
        })
        .mount("/", index)
        .launch()
        .await;
...

遇到的错误:

error[E0594]: cannot assign to data in dereference of `rocket::State<Timer>`
   --> src/main.rs:311:29
    |
311 | ...                   timer.paused = false;
    |                       ^^^^^^^^^^^^^^^^^^^^ cannot assign
    |
= help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `rocket::State<Timer>`

Rocket官方说明提到:

注意:所有托管状态必须是线程安全的。因为Rocket会自动为应用程序启用多线程,处理器可以并发访问托管状态。因此,托管状态必须是线程安全的。得益于Rust,该条件会在编译时检查,确保存储在托管状态中的类型实现Send + Sync。

但不清楚如何让状态实现Send + Sync并支持可变更新。


解决方案

核心原因

Rocket的State<T>是只读的共享状态容器,它只实现了Deref(允许读取内部数据),没有实现DerefMut(禁止直接修改内部数据),以此强制线程安全的访问规则。要修改状态,必须让内部类型本身具备线程安全的可变性。

方式一:用Mutex包裹整个结构体

Mutex是互斥锁,同一时间只允许一个线程获取锁并修改内部数据,天然保证线程安全,且自动实现Send + Sync。

修改代码如下:

use std::sync::Mutex;
use rocket::serde::{Serialize, Deserialize};

#[derive(Debug, Serialize, Deserialize)]
struct Timer {
    playing: bool,
    paused: bool,
    start_at: AtomicUsize,
}

// 请求处理器中使用&State<Mutex<Timer>>
#[post("/message", data = "<form>")]
fn message(form: Form<Message>, timer: &State<Mutex<Timer>>) {
    // 获取锁(unwrap处理简单场景,实际项目建议处理PoisonError)
    let mut timer_inner = timer.lock().unwrap();
    timer_inner.start_at.store(time, Ordering::Relaxed);
    timer_inner.playing = true;
    timer_inner.paused = true;
}

// 启动时托管Mutex包裹的Timer
let _rocket = rocket::build()
        .manage(Mutex::new(Timer {
            start_at: AtomicUsize::new(0),
            playing: false,
            paused: false,
        }))
        .mount("/", index)
        .launch()
        .await;

方式二:将普通字段替换为原子类型

如果字段之间是独立修改的,无需原子性的批量操作,可以用std::sync::atomic模块的原子类型替代普通类型,原子类型本身实现了Send + Sync,且支持无锁的线程安全修改。

修改代码如下:

use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering};
use rocket::serde::{Serialize, Deserialize};

#[derive(Debug, Serialize, Deserialize)]
struct Timer {
    playing: AtomicBool,
    paused: AtomicBool,
    start_at: AtomicUsize,
}

#[post("/message", data = "<form>")]
fn message(form: Form<Message>, timer: &State<Timer>) {
    timer.start_at.store(time, Ordering::Relaxed);
    timer.playing.store(true, Ordering::Relaxed);
    timer.paused.store(true, Ordering::Relaxed);
}

// 启动时直接托管Timer
let _rocket = rocket::build()
        .manage(Timer {
            start_at: AtomicUsize::new(0),
            playing: AtomicBool::new(false),
            paused: AtomicBool::new(false),
        })
        .mount("/", index)
        .launch()
        .await;

关于Send + Sync的说明

  • Send:标记类型可以安全地在线程间转移所有权。
  • Sync:标记类型可以安全地被多个线程共享不可变引用。
    原子类型、Mutex包裹的类型都自动实现了这两个trait,因此可以作为Rocket的托管状态通过编译。

内容的提问来源于stack exchange,提问作者CUGreen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 15:25:35