我如何在借用不可变值的匹配中发生变异?

How do I mutate in a match which borrows an immutable value?

我可以理解 borrowing/ownership Rust 中的概念,但我不知道如何解决这种情况:

use std::collections::{HashMap, HashSet};

struct Val {
    t: HashMap<u16, u16>,
    l: HashSet<u16>,
}

impl Val {
    fn new() -> Val {
        Val {
            t: HashMap::new(),
            l: HashSet::new(),
        }
    }

    fn set(&mut self, k: u16, v: u16) {
        self.t.insert(k, v);
        self.l.insert(v);
    }

    fn remove(&mut self, v: &u16) -> bool {
        self.l.remove(v)
    }

    fn do_work(&mut self, v: u16) -> bool {
        match self.t.get(&v) {
            None => false,
            Some(r) => self.remove(r),
        }
    }
}

fn main() {
    let mut v = Val::new();

    v.set(123, 100);
    v.set(100, 1234);

    println!("Size before: {}", v.l.len());
    println!("Work: {}", v.do_work(123));
    println!("Size after: {}", v.l.len());
}

playground

编译器出现错误:

error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
  --> src/main.rs:28:24
   |
26 |         match self.t.get(&v) {
   |               ------ immutable borrow occurs here
27 |             None => false,
28 |             Some(r) => self.remove(r),
   |                        ^^^^^------^^^
   |                        |    |
   |                        |    immutable borrow later used by call
   |                        mutable borrow occurs here

我不明白为什么我之前做get(读取值)时不能在match arm中变异;当通过 remove 的突变开始时,self.t.get 完成。

这是由于 get 返回的结果 (Option<&u16>) 的范围造成的吗?结果的生命周期确实在匹配表达式内有一个范围,但这种设计模式经常使用(在匹配表达式中变异)。

如何解决该错误?

您正在尝试通过使用您获得的 value 而不是 keyHashMap 中删除值。

只更改了第 26 行 Some(_) => self.remove(&v)

这会起作用:

use std::collections::HashMap;

struct Val {
    t: HashMap<u16, u16>
}

impl Val {
    fn new() -> Val {
        Val { t: HashMap::new() }
    }

    fn set(&mut self, k: u16, v: u16) {
        self.t.insert(k, v);
    }

    fn remove(&mut self, v: &u16) -> bool {
        match self.t.remove(v) {
            None => false,
            _ => true,
        }
    }

    fn do_work(&mut self, v: u16) -> bool {
        match self.t.get(&v) {
            None => false,
            Some(_) => self.remove(&v)
        }
    }
}

fn main() {
    let mut v = Val::new();

    v.set(123, 100);
    v.set(1100, 1234);

    println!("Size before: {}", v.t.len());
    println!("Work: {}", v.do_work(123));
    println!("Size after: {}", v.t.len());
}

play.rust

函数HashMap::<K,V>::get()的声明有点简化:

pub fn get<'s>(&'s self, k: &K) -> Option<&'s V>

这意味着它 returns 对包含值的可选引用,而不是值本身。由于返回的引用指向地图内部的一个值,它实际上是借用了地图,也就是说,当这个引用存在时你不能改变地图。这个限制是为了保护你,如果你在引用还活着的时候删除这个值会发生什么?

所以当你写的时候:

match self.t.get(&v) {
    None => false,
    //r: &u16
    Some(r) => self.remove(r)
}

捕获的r类型为&u16,生命周期为self.t,即借用。因此,您无法获得调用 remove.

所需的对 self 的可变引用

您的问题最简单的解决方案是 clone() 解决每个生命周期问题 模式。由于您的值是 u16 类型,即 Copy,它实际上是微不足道的:

match self.t.get(&v) {
    None => false,
    //r: u16
    Some(&r) => self.remove(&r)
}

现在 r 实际上是 u16 类型,所以它没有借用任何东西,你可以随意改变 self

如果您的 key/value 类型不是 Copy,您可以尝试 clone 它们,如果您愿意为此付费。如果没有,还有另一种选择,因为您的 remove() 函数不会修改 HashMap,而是修改不相关的 HashSet。如果你注意不要重新借用,你仍然可以改变那个集合 self:

    fn remove2(v: &u16, l: &mut HashSet<u16>) -> bool {
        l.remove(v)
    }
    fn do_work(&mut self, v: u16) -> bool {
        match self.t.get(&v) {
            None => false,
            //selt.t is borrowed, now we mut-borrow self.l, no problem
            Some(r) => Self::remove2(r, &mut self.l)
        }
    }

似乎以下解决方案适用于像这里的 u16 这样的原始类型。对于其他类型,所有权已移动。

use std::collections::HashMap;

struct Val {
    t: HashMap<u16, u16>,
}

impl Val {
    fn new() -> Val {
        Val { t: HashMap::new() }
    }

    fn set(&mut self, k: u16, v: u16) {
        self.t.insert(k, v);
    }

    fn remove(&mut self, v: &u16) -> bool {
        match self.t.remove(v) {
            None => false,
            _ => true,
        }
    }

    fn do_work(&mut self, v: u16) -> bool {
        match self.t.get(&v) {
            None => false,
            Some(&v) => self.remove(&v)
        }
    }
}

fn main() {
    let mut v = Val::new();

    v.set(123, 100);
    v.set(100, 1234);

    println!("Size before: {}", v.t.len());
    println!("Work: {}", v.do_work(123));
    println!("Size after: {}", v.t.len());
}

对于其他类型,我们必须克隆值:

use std::collections::{HashMap, HashSet};

#[derive(Debug)]
struct Val {
    t: HashMap<String, String>,
    l: HashSet<String>
}

impl Val {
    fn new() -> Val {
        Val { t: HashMap::new(), l: HashSet::new() }
    }

    fn set(&mut self, k: String, v: String) {
        self.l.insert(v.clone());
        self.t.insert(k, v);
    }

    fn remove(&mut self, v: &String) -> bool {
        self.l.remove(v)
    }

    fn do_work(&mut self, i: &String) -> bool {
        match self.t.get(i) {
            None => false,
            Some(v) => {
                let x = v.clone();

                self.remove(&x)
            }
        }
    }

    fn do_task(&mut self, i: &String) -> bool {
        match self.t.get(i) {
            None => false,
            Some(v) => self.l.insert(v.clone())
        }
    }
}

fn main() {
    let mut v = Val::new();

    v.set("AA".to_string(), "BB".to_string());
    v.set("BB".to_string(), "CC".to_string());

    println!("Start: {:#?}", v);
    println!("Size before: {}", v.l.len());
    println!("Work: {}", v.do_work(&"AA".to_string()));
    println!("Size after: {}", v.l.len());
    println!("After: {:#?}", v);
    println!("Task [Exist]: {}", v.do_task(&"BB".to_string()));
    println!("Task [New]: {}", v.do_task(&"AA".to_string()));
    println!("End: {:#?}", v);
}

但我想要一个没有分配的解决方案