Hello,
Is it possible to have mutable reference to State<T>?
E.g., I have following request handler:
#[get("/")]
fn index(x: State<X>)
And inside that handler I'd like to pass x to another function as &mut x is it possible?
If not then what should I do in this situation? Is there a workaround?
To expand on @belst's response: you cannot obtain an &mut T reference from a State<T> as doing so would be racy. This is because requests are handled concurrently, so allowing this would allow two &mut T references to the same T to be active concurrently.
To get access to a mutable T, you need to use some form of internal mutability. Among others, these include the types in sync.
Could anyone show some code for the question?
@dofork You can manage and use a Mutex with something like this:
use std::sync::Mutex;
struct SharedData { numbers: Mutex<Vec<i32>> };
#[get("/<number>")]
fn add_number(number: i32, shared: State<SharedData>) -> String {
let mut lock = shared.lock().expect("lock shared data");
lock.numbers.push(number);
format!("There are now {} numbers", lock.numbers.len())
}
fn main() {
rocket::ignite()
//...
.manage(SharedData { numbers: Mutex::new(vec![5]) })
.launch();
}
Using an RwLock would work in a similar way. RwLock and Mutex might be too slow if you will have a lot of concurrent operations on the same shared data, however.
@dofork You can manage and use a Mutex with something like this:
use std::sync::Mutex; struct SharedData { numbers: Mutex<Vec<i32>> }; #[get("/<number>")] fn add_number(number: i32, shared: State<SharedData>) -> String { let lock = shared.lock().expect("lock shared data"); lock.numbers.push(number); format!("There are now {} numbers", lock.numbers.len()) } fn main() { rocket::ignite() //... .manage(SharedData { numbers: Mutex::new(vec![5]) }) .launch(); }Using an
RwLockwould work in a similar way.RwLockandMutexmight be too slow if you will have a lot of concurrent operations on the same shared data, however.
It seems that when you push a number with lock.numbers.push(number), it has to be mutable, but rocket doesn't allow that. Am I missing something here?
Am I missing something here?
Probably a mut between let and lock: let mut lock = shared.lock().expect("lock shared data");
If that works, I will go back and edit that example.
@emryx26
Mutex and RwLock locks acquired by their respective lock methods have to be mutable if you want to push an item.
this is my working example:
// src/cache.rs
use std::collections::HashMap;
use std::sync::{RwLock, RwLockReadGuard};
use std::sync::atomic::{AtomicI32, AtomicU32};
use std::sync::atomic::Ordering::Relaxed as ordering;
use super::models::Hook;
#[derive(Hash, Eq, PartialEq, Debug)]
pub struct LogPage {
dt: u32,
sid: i32,
pn: i32,
}
impl LogPage {
pub fn new(d: &u32, s: &i32, p: &i32) -> LogPage {
LogPage { dt: *d, sid: *s, pn: *p }
}
}
#[derive(Serialize)]
pub struct Context {
items: Vec<Hook>,
page: i32,
total_page: i32
}
impl Context {
pub fn new(v: &Vec<Hook>, p: &i32, tp: &i32 ) -> Context {
let vec = v.to_vec();
Context{ items: vec, page: *p, total_page: *tp }
}
}
pub struct Cache {
date: AtomicU32,
counter: AtomicI32,
map: RwLock<HashMap< LogPage, Context >>
}
impl Cache {
// init
pub fn new() -> Cache {
Cache {
date: AtomicU32::new(0),
counter: AtomicI32::new(0),
map: RwLock::new(HashMap::new())
}
}
// getter
pub fn date(&self) -> u32 {
self.date.load(ordering)
}
pub fn counter(&self) -> i32 {
self.counter.load(ordering)
}
pub fn map(&self) -> RwLockReadGuard<HashMap< LogPage, Context >> {
self.map.read().unwrap()
}
// set values
pub fn set_date(&self, v: &u32) {
self.date.store(*v, ordering);
}
pub fn set_counter(&self, v: &i32) {
self.counter.store(*v, ordering);
}
pub fn fetch_inc_counter(&self) -> i32 {
self.counter.fetch_add(1, ordering)
}
pub fn map_mut(&self, k: LogPage, v: Context) {
let mut lock = self.map.write()
.expect("locking cache to write");
lock.insert(k, v);
}
}
then in your route function:
#[get("/logs?<p>")]
pub fn get_logs(
conn: DbConn,
p: Option<i32>,
c: State<Cache>,
) -> Template {
let pn = p.map(|p| std::cmp::max(p,1)).unwrap_or(1);
let log_page = LogPage::new(&c.date(), &c.counter(), &pn);
if let Some(v) = c.map().get(&log_page) {
info!("return cached context");
return Template::render("index", &v);
}
info!("no cache, getting from db");
let (items, tot) = Hook::all(&*conn, pn);
// insert
info!("caching new context");
let cached = Context::new(&items, &pn, &tot);
c.map_mut(log_page, cached);
let context = Context::new(&items, &pn, &tot);
Template::render("index", &context)
}
@dofork You can manage and use a Mutex with something like this:
use std::sync::Mutex; struct SharedData { numbers: Mutex<Vec<i32>> }; #[get("/<number>")] fn add_number(number: i32, shared: State<SharedData>) -> String { let mut lock = shared.lock().expect("lock shared data"); lock.numbers.push(number); format!("There are now {} numbers", lock.numbers.len()) } fn main() { rocket::ignite() //... .manage(SharedData { numbers: Mutex::new(vec![5]) }) .launch(); }Using an
RwLockwould work in a similar way.RwLockandMutexmight be too slow if you will have a lot of concurrent operations on the same shared data, however.
Modified to compiling+working:
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use]
extern crate rocket;
use rocket::State;
use std::sync::Mutex;
struct SharedData {
numbers: Mutex<Vec<i32>>,
}
#[get("/<number>")]
fn add_number(number: i32, shared: State<SharedData>) -> String {
let shared_data: &SharedData = shared.inner();
shared_data.numbers.lock().unwrap().push(number);
format!(
"There are now {} numbers",
shared_data.numbers.lock().unwrap().len()
)
}
fn main() {
rocket::ignite()
.mount("/", routes![add_number])
.manage(SharedData {
numbers: Mutex::new(vec![5]),
})
.launch();
}
I wasn't able to move the vector out of the struct, since its not implementing the copy trait. Wasn't able to resolve that, but directly accessing it works for now ;) Thanks
EDIT:
Here's another slightly modified example, having a second thread modify the numbers vector every second:
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use]
extern crate rocket;
use rocket::State;
use std::sync::{Arc, Mutex};
use std::thread::{sleep, spawn};
use std::time::Duration;
struct SharedData {
numbers: Arc<Mutex<Vec<i32>>>,
}
#[get("/<number>")]
fn add_number(number: i32, shared: State<SharedData>) -> String {
let shared_data: &SharedData = shared.inner();
shared_data.numbers.lock().unwrap().push(number);
format!(
"There are now {} numbers",
shared_data.numbers.lock().unwrap().len()
)
}
fn main() {
let numbers = Arc::new(Mutex::new(vec![5]));
let numbers_arc = Arc::clone(&numbers);
let _ = spawn(move || loop {
sleep(Duration::new(1, 0));
numbers_arc.lock().unwrap().push(9999);
println!(
"There are now {} numbers",
numbers_arc.lock().unwrap().len()
)
});
rocket::ignite()
.mount("/", routes![add_number])
.manage(SharedData { numbers: numbers })
.launch();
}
Most helpful comment
You need to use inner mutability via a Mutex or RwLock