Lifetime is not dropped after `if let (x) { return x }`

I'm trying to implement kind of cache, and for that I do:

  1. If value already in cache, return reference to it.
  2. If value not in cache, insert it and return reference to inserted value.

Like this:

// 1 case
if Some(ref_to_value) = self.inner.get(key) {
    return ref_to_value;

// 2 case

But after first case inner still considered borrowed as immutable, so I can't insert new value.

What am I missing?

Complete example

(don't mind single v in arguments, it just for example)

use std::collections::HashMap;

struct S {
    inner: HashMap<String, String>,

impl S {
    fn get_value(&mut self, v: &str) -> &str {
        if let Some(v) = self.inner.get(v) {
            return v;
        // Why `self.inner` still borrowed at this point?
        self.inner.entry(v.into()).or_insert_with(|| v.to_string())



   Compiling playground v0.0.1 (/playground)
error[E0502]: cannot borrow `self.inner` as mutable because it is also borrowed as immutable
  --> src/
8  |     fn get_value<'a>(&'a mut self, v: &str) -> &'a str {
   |                  -- lifetime `'a` defined here
9  |         if let Some(v) = self.inner.get(v) {
   |                          ---------- immutable borrow occurs here
10 |             return v;
   |                    - returning this value requires that `self.inner` is borrowed for `'a`
14 |         self.inner.entry(v.into()).or_insert_with(|| v.to_string())
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0502`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

This is a known limitation of the current borrow checker. The next-generation borrow checker Polonius will be able to compile this code successfully. You can try the experimental version in the nightly toolchain by compiling with rustc -Z polonius.

For now, if you don't want to clone the key unnecessarily, one workaround is to do something like the following, at the cost of an extra lookup:

    fn get_value(&mut self, v: &str) -> &str {
        if self.inner.contains_key(v) {
            return self.inner.get(v).unwrap()
        self.inner.entry(v.into()).or_insert_with(|| v.to_string())

So we have this signature:

fn get_value<'a>(&'a mut self, v: &str) -> &'a str

The explanation for why the borrow-checker does this is that to return v, it must have the type &'a str, but lifetimes specified with generic parameters always enclose the entire function, so to produce an &'a str from get, it must borrow inner for 'a, and that lifetime encloses the entire function.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.