use std::collections::HashMap;
#[derive(Default)]
struct Foo {
map: HashMap<String, String>,
}
fn calc_value() -> String {
"ololo".into()
}
impl Foo {
pub fn get_or_create(&mut self, key: String) -> &str {
self.map.entry(key).or_insert_with(calc_value)
}
pub fn print(&self, value: &str) {
println!("{}", value)
}
}
fn main() {
let mut foo = Foo::default();
let value = foo.get_or_create("key".into());
foo.print(value);
}
error[E0502]: cannot borrow `foo` as immutable because it is also borrowed as mutable
--> src/main.rs:24:5
|
23 | let value = foo.get_or_create("key".into());
| --- mutable borrow occurs here
24 | foo.print(value);
| ^^^ ----- mutable borrow later used here
| |
| immutable borrow occurs here
This code gives error, because I use reference(value), derived from mutable borrow of self(let value = foo.get_or_create("key".into());) when I want to get immutable borrow of self (foo.print(value);). AKAIK it is because lifetime of mutable borrow of self is prolnged until derived reference (returned from get_or_create) is alive. I don't understand the reason behind that, especially why prolonged borrow is considered mutable, while derived reference is immutable. Is it just limitation of current implementation of borrowchk or there is some unsoundness in doing &mut self -> &self conversion here unconditionally?
The reason for why this is disallowed is because of the presense of shared mutability. For example Mutex::get_mut gives a reference to the guarded value without locking the mutex.
So if you could get aliasing unique references like so if this were allowed
fn get_ref(m: &mut Mutex<T>) -> &T {
m.get_mut()
}
let a = get_ref(&mut mutex);
let b = mutex.lock();
// a aliases b here, which would be UB
This means that the &mut self borrow passed in must live as long as the &str value that gets returned.
There's currently no way to say "this function borrows self mutably/exclusively for one lifetime, and immutably/shared for a different lifetime." This is a known limitation of lifetime syntax.
The function takes a &mut self, so self will be mutably borrowed for the required lifetime, which must be as long as the return value lives.
The compiler does not and cannot "downgrade" borrows like you propose.
The solutions are:
Create and call a second function that takes &self and does the lookup again from scratch, and then use the reference from that instead of the one from get_or_create.
Do such a second lookup in get_or_create and then return both the new reference and a copy of self with type &Self, then in the caller use a let statement to override the binding with the new return value
Restructure the code so you don't need to access the struct while holding the reference to the value
Ah, I see, seems that interior mutability breaks my idea. Just for clarity and for anybody who will read that thread: your example will not compile only if a is used after mutex lock, for example it that code
let a = get_ref(&mut mutex);
let b = mutex.lock();
drop(a)
Otherwise, compiler will drop a reference before lock, thanks NLL