0

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.

5
  • 1
    Did you read about Arc and Mutex? That can be good start to read doc.rust-lang.org/std/sync/struct.Arc.html and doc.rust-lang.org/std/sync/struct.Mutex.html Commented May 16, 2023 at 21:07
  • 1
    @ĐorđeZeljić In async context you want to use an async mutex. E.g. tokio has tokio::sync::Mutex. Commented May 16, 2023 at 21:36
  • 1
    @cdhowie That is not generally true: "Contrary to popular belief, it is ok and often preferred to use the ordinary Mutex from the standard library in asynchronous code." Commented May 16, 2023 at 21:51
  • @drewtato So does tokio do some trickery to keep a sync mutex from blocking a task thread, or...? Commented May 17, 2023 at 9:27
  • @cdhowie no, it's simply that most uses of mutexes aren't locked very often or for very long. Commented May 17, 2023 at 19:39

1 Answer 1

1

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?;
        // ...
    }
});
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.