I'm currently building a simple in-memory database to get back into 'learning' rust, but for the life of me I cannot seem to figure out a work around for what I'm trying to do.
I'm using tokio for asynchronous io/networking.
Here's the code:
let mut cache = Cache {
buffer: vec![0; allocated],
mapping: HashMap::new()
};
let listener = TcpListener::bind("0.0.0.0:9055").await?;
loop {
let (mut socket, _) = listener.accept().await?;
tokio::spawn(async move {
process_client(&mut cache, &mut socket).await?;
Ok::<_, io::Error>(())
});
}
Definition of the Cache struct:
struct Cache {
buffer: Vec<u8>,
mapping: HashMap<String, (usize, usize)>
}
Now the error I keep getting is this:
error[E0382]: use of moved value: `cache`
--> src\main.rs:172:22
|
162 | let mut cache = Cache {
| --------- move occurs because `cache` has type `Cache`, which does not implement the `Copy` trait
...
172 | tokio::spawn(async move {
| ______________________^
173 | | process_client(&mut cache, &mut socket).await?;
| | ----- use occurs due to use in generator
174 | | Ok::<_, io::Error>(())
175 | | });
| |_________^ value moved here, in previous iteration of loop
How can I pass a mutable reference of cache
to the process_client()
method in this async task without having to clone/copy the entire cache
struct? (since cloning/copying would be a huge performance hit if the buffer was large)
I've tried to implement lifetimes, but I do not have a good understanding of lifetimes and how to use them properly.
Without some form of synchronisation this cannot be done. This is by design, the compiler is preventing you from a potential race condition. Consider the case where tokio is running on multiple threads, the spawn gets hit and deferred to a different thread, the loop then repeats and the second spawn is hit and deferred to a different thread. You now have two mutable references to cache
on two different threads, with potentially concurrent accesses.
To solve this you can use something like Mutex
, in this case you should use tokio::sync::Mutex
(or RwLock
if you ever only need immutable borrows), along with an Arc
(as otherwise there is no way to ensure that cache
will live long enough and allow multiple valid references to it). For example:
let mut cache = Arc::new(Mutex::new(Cache {
buffer: vec![0; allocated],
mapping: HashMap::new()
}));
// ...
tokio::spawn({
let cache = cache.clone();
async move {
process_client(&mut cache.lock().await, &mut socket).await?;
// ...
}
});