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
相关产品推荐
相关产品推荐

