Borrow checker confusion

This is of course a made up example. But does not compile stating "cannot borrow as immutable after a mutable borrow". Could someone please explain this? And how can this be fixed?

fn fill<'a>(key : &'a String, set : &'a mut HashSet<&'a String>) -> ()
{
    set.insert(key);
}
fn main()
{
    let mut set = HashSet::<&String>::new();
    let key = "hello".into();
    fill(&key, &mut set);
    println!("{}", set.len());
}

That’s a huge anti-pattern. Try to avoid using the same lifetime on different unrelated things or on different levels of the same thing like that, especially if the outer level is a mutable reference. It’s effectively borrowing the HashSet for its entire existence. (A HashSet<&'a String> can only exist for as long as the &'a String references are alive, but you’re also borrowing it for the entire lifetime 'a, hence it’s borrowed for the entire remainder of its existence.)

Fix:

fn fill<'a>(key : &'a String, set : &mut HashSet<&'a String>) -> ()
{
    set.insert(key);
}

or equivalently

fn fill<'a, 'b>(key : &'a String, set : &'b mut HashSet<&'a String>) -> ()
{
    set.insert(key);
}

Actually, &String is an antipattern itself, too. It’s usually recommended to use &str instead of &String. A &String can always (implicitly) be converted into a &str (but not the other way), and &str is also slighly more performant because it avoids an additional level of indirection.


Finally, depending on your use-case you might not want to put borrowed strings into the HashSet, but owned ones, especially if the set is supposed to be long-lived or the strings that are borrowed are short-lived. So using HashSet<String>, and fill(key : String, set : &mut HashSet<String>) could be an option, too, depending on the use-case.

4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.