rustmutable

How can I introduce a copied variable as mutable in a if-let statement?


I have a HashMap<i8, i8> which could contain cycles:

let mut x: HashMap<i8, i8> = HashMap::new();
x.insert(1, 6);
x.insert(3, 5);
x.insert(5, 1);

To get the final value for 3, it should first lookup x[3], then x[5] and finally x[1] which should yield 6. I decided to use a while let loop:

let mut y = x[&3]; // y: i8
while let Some(&z) = x.get(&y) {
    y = z;
}

println!("{}", y);

x.insert(0, 0);

This works fine, but it would panic! if 3 is not in the map. As I don't want to do anything about the None case, I want to use a if let (similar to the while let used).

I have tried some notations:

  1. if let Some(&y) = x.get(&3): copies the value, but y is immutable (y: i8)
  2. if let Some(mut y) = x.get(&3): y is mutable, but the value is borrowed (mut y: &i8)
  3. if let mut Some(&y) = x.get(&3): my target: mutable copy, but invalid syntax (mut y: i8)

(All variants are available at Rust Playground, but you need to comment out the third try, as it is invalid syntax)

I would not argue about the second variant, but I need to insert values into my map in the body of the if let. As the map remains borrowed, I can't insert anymore. All I would need is that the value in Some(y) is copied, and y is mutable, so that the borrow checker is satisfied and I can do my recursive lookups.


Solution

  • Your approach #1 is a perfectly correct match, you just need to make the y variable mutable. One possibility is to convert Option<&i8> to Option<i8>, enabling the use of mut y in the pattern. For example, Option::map can dereference the value:

    if let Some(mut y) = x.get(&3).map(|ref| *ref) {
    

    Since Copy implies (cheap) Clone, you can express the same using Option::cloned():

    if let Some(mut y) = x.get(&3).cloned() {
    

    As of Rust 1.35, you can use Option::copied(), which is guaranteed to just copy the value (and fails to compile if the value is not Copy):

    if let Some(mut y) = x.get(&3).copied() {
    

    Another possibility is to leave your approach #1 as-is, but correct it simply by introducing a separate mutable variable inside the if let block:

    if let Some(&y) = x.get(&3) {
        let mut y = y;
        ...