How do I write a rust function that can both read and write to a cache?

时光毁灭记忆、已成空白 提交于 2020-01-16 14:39:48

问题


Original Problem Statement

I'm trying to write a function that can both read and write from a cache, but I'm running into a problem where the compiler says I can't both mutably and immutably borrow the cache.

I've read through https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html , https://naftuli.wtf/2019/03/20/rust-the-hard-parts/ and random stack overflow/Reddit posts, but I can't see how to apply what they say to this code.

use std::collections::HashMap;

struct CacheForMoves {
    set_of_moves: Vec<usize>,
    cache: HashMap<usize, Vec<Vec<usize>>>,
}

impl CacheForMoves {
    fn new(set_of_moves: Vec<usize>) -> CacheForMoves {
        CacheForMoves {
            set_of_moves: set_of_moves,
            cache: HashMap::new(),
        }
    }

    fn get_for_n(&self, n: usize) -> Option<&Vec<Vec<usize>>> {
        self.cache.get(&n)
    }

    fn insert_for_n(&mut self, n: usize, value: Vec<Vec<usize>>) {
        self.cache.insert(n, value);
    }
}

fn stairs(cache: &mut CacheForMoves, n: usize) -> &Vec<Vec<usize>> {
    return match cache.get_for_n(n) {
        Some(result) => result,
        None => stairs(cache, n - 1),
    };
}

fn main() {
    let mut cache = CacheForMoves::new(vec![1, 2]);
    cache.insert_for_n(1, vec![]);
    let result = stairs(&mut cache, 4);
    println!("Found {} possible solutions: ", result.len());
    for solution in result {
        println!("{:?}", solution);
    }
}

This produces the following compile error:

error[E0502]: cannot borrow `*cache` as mutable because it is also borrowed as immutable
  --> stairs2.rs:28:18
   |
26 |     return match cache.get_for_n(n) {
   |                  ----- immutable borrow occurs here
27 |         Some(result) => result,
28 |         None => stairs(cache, n - 1)
   |                        ^^^^^ mutable borrow occurs here
29 |     }
30 | }
   | - immutable borrow ends here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0502`.

I don't understand why it thinks I'm immutably borrowing cache on line 26. My understanding is main creates an instance of CacheForMove and owns that value. It's mutably-lending the value to the stairs function, and so now stairs has mutably borrowed the value. I expected to be able to invoke both get_for_n and insert_for_n on that mutably-borrowed reference.

Answers that I don't understand yet

Is this a duplicate of How can I mutate other elements of a HashMap when using the entry pattern? ?

In this SO question, the OP wants to have their update for one key in the cache depend on the value of a different key in the cache. I do eventually want to do that to, but I'm running into a problem before I get to that point. I'm not looking at other entries in the cache in order to compute "this" entry. The answers in that question says that they would need to split getting from the cache from inserting into the cache like so:

fn compute(cache: &mut HashMap<u32, u32>, input: u32) -> u32 {
    if let Some(entry) = cache.get(&input) {
        return *entry;
    }

    let res = if input > 2 {
        // Trivial placeholder for an expensive computation.
        compute(cache, input - 1) + compute(cache, input - 2)
    } else {
        0
    };
    cache.insert(input, res);
    res
}

However, I believe my code already splits getting from inserting, and yet I still get a compile error.

Even if I adapt the above example to match my API:

fn stairs(cache: &mut CacheForMoves, n: usize) -> &Vec<Vec<usize>> {
    if let Some(entry) = cache.get_for_n(n) {
        return entry;
    }
    let res = stairs(cache, n - 1);
    cache.insert_for_n(n, res.clone());
    res
}

I still get the same error:

error[E0502]: cannot borrow `*cache` as mutable because it is also borrowed as immutable
  --> src/main.rs:29:15
   |
25 | fn stairs(cache: &mut CacheForMoves, n: usize) -> &Vec<Vec<usize>> {
   |                  - let's call the lifetime of this reference `'1`
26 |     if let Some(entry) = cache.get_for_n(n) {
   |                          ----- immutable borrow occurs here
27 |         return entry;
   |                ----- returning this value requires that `*cache` is borrowed for `'1`
28 |     }
29 |     let res = stairs(cache, n - 1);
   |               ^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

error[E0499]: cannot borrow `*cache` as mutable more than once at a time
  --> src/main.rs:30:5
   |
25 | fn stairs(cache: &mut CacheForMoves, n: usize) -> &Vec<Vec<usize>> {
   |                  - let's call the lifetime of this reference `'1`
...
29 |     let res = stairs(cache, n - 1);
   |                      ----- first mutable borrow occurs here
30 |     cache.insert_for_n(n, res.clone());
   |     ^^^^^ second mutable borrow occurs here
31 |     res
   |     --- returning this value requires that `*cache` is borrowed for `'1`

error: aborting due to 2 previous errors

Some errors occurred: E0499, E0502.
For more information about an error, try `rustc --explain E0499`.

Is this a duplicate of What is the idiomatic way to implement caching on a function that is not a struct method? ?

In that SO question, the OP states they are unwilling to use a struct, and the answers provided use some combination of unsafe, mutex, lazy_static!, RefCell, and so on.

I have the opposite issue. I am perfectly willing to use a struct (and in fact, I am using one in my original problem statement), but using unsafe, mutex, lazy_static!, and so on sound much more dangerous or complex to me.

The OP of that question implies that if we could use a struct, the solution would be obvious. I would like to learn that obvious solution.

you are immutable borrowing it - to run get_for_n method, which borrows from self and releases this borrow, when returned value gets out of scope (that is, at the end of the match). You don't want the matched value to be invalidated by whatever the stairs function do to the cache.

The matched value is not used by whatever the stairs function does. In the implementation shown in the original problem statement:

fn stairs(cache: &mut CacheForMoves, n: usize) -> &Vec<Vec<usize>> {
    return match cache.get_for_n(n) {
        Some(result) => result,
        None => stairs(cache, n - 1),
    };
}

I immutably borrow cache to get a cached value out of it. If there is a value available, I return it (without recursively calling stairs again). If there is no value, I expect None to be copyable (i.e. I can have my own copy of None on my stack; I no longer need to reference any data in cache at all). At this point, I expect to be able to safely mutably borrow cache to invoke stairs(cache, n-1), because there are no other borrows (mutable or immutable) to cache.

To really drive this point home, consider this alternative implementation of the stairs function:

fn stairs(cache: &mut CacheForMoves, n: usize) -> &Vec<Vec<usize>> {
    {
        let maybe_result = cache.get_for_n(n);
        if maybe_result.is_some() {
            return maybe_result.unwrap();
        }
    }
    return stairs(cache, n - 1);
}

Here I've used a pair of curly braces to limit the scope of the immutable borrow. I perform an immutable borrow to populate maybe_result, and check whether it is a Some. If it is, I unwrap the internal value and return it. If not, I end my scope, and thus all borrows have gone out of scope and are now invalid. There are no borrows happening anymore.

Then, I try to mutably borrow cache to recursively invoke stairs. This should be the only borrow happening at this point, and so I expect this borrow to succeed, but the compiler tells me:

error[E0502]: cannot borrow `*cache` as mutable because it is also borrowed as immutable
  --> src/main.rs:32:12
   |
25 | fn stairs(cache: &mut CacheForMoves, n: usize) -> &Vec<Vec<usize>> {
   |                  - let's call the lifetime of this reference `'1`
26 |     {
27 |         let maybe_result = cache.get_for_n(n);
   |                            ----- immutable borrow occurs here
28 |         if maybe_result.is_some() {
29 |             return maybe_result.unwrap();
   |                    --------------------- returning this value requires that `*cache` is borrowed for `'1`
...
32 |     return stairs(cache, n - 1);
   |            ^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0502`.

回答1:


Checking for None explicitly and returning before the immutable borrow works:

fn stairs(cache: &mut CacheForMoves, n: usize) -> &Vec<Vec<usize>> {
    if cache.get_for_n(n).is_none() {
        return stairs(cache, n - 1);
    } else {
        cache.get_for_n(n).unwrap()
    }
}

However I don't like having to call the get_for_n() function twice

Rust playground link




回答2:


Wrapping it in a Rc is a possible solution.

Rc is a "reference counted" pointer, and enables you to have multiple "borrows" to the same value. The count will be increased when you will invoke the "clone" method. When value is destroyed, the count will be decreased. And finally if the reference count hits 0, the pointer and its "pointed" value are destroyed. You may want to use an Arc in a concurrent environment (it basically is an Atomically reference counted" pointer) or if you are making a crate, as it gives more flexibility. Arc will do the same work as Rc, except that count will be done atomically.

That way, your ownership problem will be solved, without copying the whole Vec but by just having another pointer to the same "value".

I also replaced by use of Option::unwrap_or_else, which is a more idiomatic way to unwrap an Option::Some(T), or compute lazily a default value in case of a Option::None.

use std::collections::HashMap;
use std::rc::Rc;

struct CacheForMoves {
    set_of_moves: Vec<usize>,
    cache: HashMap<usize, Vec<Vec<usize>>>,
}

impl CacheForMoves {
    fn new(set_of_moves: Vec<usize>) -> CacheForMoves {
        CacheForMoves {
            set_of_moves,
            cache: HashMap::new(),
        }
    }

    fn get_for_n(&self, n: usize) -> Option<&Vec<Vec<usize>>> {
        self.cache.get(&n)
    }

    fn insert_for_n(&mut self, n: usize, value: Vec<Vec<usize>>) {
        self.cache.insert(n, value);
    }
}

fn stairs(cache: &Rc<CacheForMoves>, n: usize) -> &Vec<Vec<usize>> {
    cache.get_for_n(n).unwrap_or_else(|| stairs(cache, n - 1))
}

fn main() {
    let mut cache = Rc::new(CacheForMoves::new(vec![1, 2]));
    Rc::get_mut(&mut cache).unwrap().insert_for_n(1, vec![]);
    let result = stairs(&cache, 4);
    println!("Found {} possible solutions: ", result.len());
    for solution in result {
        println!("{:?}", solution);
    }
}



回答3:


I think I've got this figured out, so recording my answer in case someone else gets stuck with the same problem. This compiles and runs:

use std::collections::HashMap;

struct CacheForMoves {
    set_of_moves: Vec<usize>,
    cache: HashMap<usize, Vec<Vec<usize>>>
}

impl CacheForMoves {
    fn new(set_of_moves: Vec<usize>) -> CacheForMoves {
        CacheForMoves {
            set_of_moves: set_of_moves,
            cache: HashMap::new()
        }
    }

    fn get_for_n(&self, n: usize) -> Option<&Vec<Vec<usize>>> {
        self.cache.get(&n)
    }

    fn insert_for_n(&mut self, n: usize, value: Vec<Vec<usize>>) {
        self.cache.insert(n, value);
    }
}

fn stairs(cache: &mut CacheForMoves, n: usize) -> Vec<Vec<usize>> {
    return match cache.get_for_n(n) {
        Some(result) => result.clone(),
        None => stairs(cache, n - 1)
    }
}

fn main() {
    let mut cache = CacheForMoves::new(vec![1, 2]);
    cache.insert_for_n(1, vec![]);
    let result = stairs(&mut cache, 4);
    println!("Found {} possible solutions: ", result.len());
    for solution in result {
        println!("{:?}", solution);
    }
}

There are 2 main changes:

  1. stairs no longer returns &Vec<Vec<usize>> and instead returns Vec<Vec<usize>>
  2. In the Some(result) case, we return result.clone() instead of result.

2 is a consequence of 1, so let's focus on why 1 is necessary and why it fixes the problem. The HashMap owns the Vec<Vec<usize>>, and so when the original implementation returned a &Vec<Vec<usize>>, it was returning a reference to a memory location owned by the HashMap. If someone were to mutate the HashMap, say by deleting an entry, since the HashMap owns the Vec<Vec<usize>>, the HashMap would conclude that it's safe to deallocate the memory being used by the Vec<Vec<usize>>, and I'd end up with a dangling reference.

I can only return a &Vec<Vec<usize>> as long as I can guarantee no one will mutate the HashMap for as long as the &Vec<Vec<usize>> reference exists, and since I'm returning the &Vec<Vec<usize>> reference to my caller, that essentially means I need to guarantee that the HashMap is immutable forever (since I have no idea what the caller might do).



来源:https://stackoverflow.com/questions/55859983/how-do-i-write-a-rust-function-that-can-both-read-and-write-to-a-cache

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!