简体   繁体   English

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

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

I can understand borrowing/ownership concepts in Rust, but I have no idea how to work around this case:我可以理解 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 操场

The compiler has the error:编译器有错误:

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

I don't understand why I can't mutate in the match arm when I did a get (read value) before;我不明白为什么我之前执行get (读取值)时无法在 match 臂中发生变异; the self.t.get is finished when the mutation via remove begins. self.t.get在通过remove开始突变时完成。

Is this due to scope of the result ( Option<&u16> ) returned by the get ?这是由于get返回的结果( Option<&u16> )的范围吗? It's true that the lifetime of the result has a scope inside the match expression, but this design-pattern is used very often (mutate in a match expression).结果的生命周期确实在匹配表达式内有一个范围,但这种设计模式经常使用(在匹配表达式中发生变异)。

How do I work around the error?我该如何解决该错误?

The declaration of function HashMap::<K,V>::get() is, a bit simplified:函数HashMap::<K,V>::get()的声明有点简化:

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

This means that it returns an optional reference to the contained value, not the value itself.这意味着它返回对所包含值的可选引用,而不是值本身。 Since the returned reference points to a value inside the map, it actually borrows the map, that is, you cannot mutate the map while this reference exists.由于返回的引用指向映射内部的一个值,它实际上是借用了映射,也就是说,当这个引用存在时,你不能改变映射。 This restriction is there to protect you, what would happen if you remove this value while the reference is still alive?这个限制是为了保护你,如果你在引用还活着的时候删除这个值会发生什么?

So when you write:所以当你写:

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

the captured r is of type &u16 and its lifetime is that of self.t , that is, it is borrowing it.捕获的r&u16类型,它的生命周期是self.t生命周期,也就是说,它正在借用它。 Thus you cannot get a mutable reference to self , that is needed to call remove.因此,您无法获得对self的可变引用,这是调用 remove 所必需的。

The simplest solution for your problem is the clone() solves every lifetime issue pattern.您的问题最简单的解决方案是clone()解决每个生命周期问题模式。 Since your values are of type u16 , that is Copy , it is actually trivial:由于您的值属于u16类型,即Copy ,因此实际上很简单:

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

Now r is actually of type u16 so it borrows nothing and you can mutate self at will.现在r实际上是u16类型,所以它什么都不借用,你可以随意改变self

If your key/value types weren't Copy you could try and clone them, if you are willing to pay for that.如果您的键/值类型不是Copy您可以尝试clone它们,如果您愿意为此付费。 If not, there is still another option as your remove() function does not modify the HashMap but an unrelated HashSet .如果没有,还有另一种选择,因为您的remove()函数不会修改HashMap而是修改不相关的HashSet You can still mutate that set if you take care not to reborrow self :如果您注意不要重新借用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)
        }
    }

You are trying to remove value from HashMap by using value you get, not key .您正在尝试使用您获得的value而不是keyHashMap删除值。

Only line 26 is changed Some(_) => self.remove(&v)仅第 26 行更改Some(_) => self.remove(&v)

This will work:这将起作用:

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 play.rust

It seems that the following solution is good for primitive types like here u16.似乎以下解决方案适用于像这里 u16 这样的原始类型。 For other types, the ownership is moved.对于其他类型,所有权已转移。

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());
}

For other types, we must clone the value:对于其他类型,我们必须克隆值:

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);
}

But i'd like a solution that has no allocation但我想要一个没有分配的解决方案

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM