简体   繁体   English

Rust:修改 HashMap 中的值,同时不变地借用整个 HashMap

[英]Rust: Modify value in HashMap while immutably borrowing the whole HashMap

I'm trying to learn Rust by using it in a project of mine.我正在尝试通过在我的一个项目中使用 Rust 来学习它。 However, I've been struggling with the borrow checker quite a bit in some code which has a very similar form to the following:但是,在某些代码中,我一直在努力使用借用检查器,这些代码的形式与以下内容非常相似:

use std::collections::HashMap;
use std::pin::Pin;
use std::vec::Vec;

struct MyStruct<'a> {
    value: i32,
    substructs: Option<Vec<Pin<&'a MyStruct<'a>>>>,
}

struct Toplevel<'a> {
    my_structs: HashMap<String, Pin<Box<MyStruct<'a>>>>,
}

fn main() {
    let mut toplevel = Toplevel {
        my_structs: HashMap::new(),
    };

    // First pass: add the elements to the HashMap
    toplevel.my_structs.insert(
        "abc".into(),
        Pin::new(Box::new(MyStruct {
            value: 0,
            substructs: None,
        })),
    );
    toplevel.my_structs.insert(
        "def".into(),
        Pin::new(Box::new(MyStruct {
            value: 5,
            substructs: None,
        })),
    );
    toplevel.my_structs.insert(
        "ghi".into(),
        Pin::new(Box::new(MyStruct {
            value: -7,
            substructs: None,
        })),
    );

    // Second pass: for each MyStruct, add substructs
    let subs = vec![
        toplevel.my_structs.get("abc").unwrap().as_ref(),
        toplevel.my_structs.get("def").unwrap().as_ref(),
        toplevel.my_structs.get("ghi").unwrap().as_ref(),
    ];
    toplevel.my_structs.get_mut("abc").unwrap().substructs = Some(subs);
}

When compiling, I get the following message:编译时,我收到以下消息:

error[E0502]: cannot borrow `toplevel.my_structs` as mutable because it is also borrowed as immutable
  --> src/main.rs:48:5
   |
44 |         toplevel.my_structs.get("abc").unwrap().as_ref(),
   |         ------------------- immutable borrow occurs here
...
48 |     toplevel.my_structs.get_mut("abc").unwrap().substructs = Some(subs);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^--------------------
   |     |
   |     mutable borrow occurs here
   |     immutable borrow later used here

I think I understand why this happens: toplevel.my_structs.get_mut(...) borrows toplevel.my_structs as mutable.我想我明白为什么会发生这种情况: toplevel.my_structs.get_mut(...)借用toplevel.my_structs作为可变的。 However, in the same block, toplevel.my_structs.get(...) also borrows toplevel.my_structs (though this time as immutable).然而,在同一个块中, toplevel.my_structs.get(...)也借用了toplevel.my_structs (虽然这次是不可变的)。

I also see how this would indeed be a problem if the function which borrows &mut toplevel.my_structs , say, added a new key.我还看到,如果借用&mut toplevel.my_structs的函数添加了一个新键,这确实是一个问题。

However, all that is done here in the &mut toplevel.my_structs borrow is modify the value corresponding to a specific key, which shouldn't change memory layout (and that's guaranteed, thanks to Pin ).然而,在&mut toplevel.my_structs借用中所做的只是修改与特定键对应的值,这不应该改变内存布局(这是有保证的,感谢Pin )。 Right?对?

Is there a way to communicate this to the compiler, so that I can compile this code?有没有办法将其传达给编译器,以便我可以编译此代码? This appears to be somewhat similar to what motivates the hashmap::Entry API, but I need to be able to access other keys as well, not only the one I want to modify.这似乎有点类似于hashmap::Entry API 的动机,但我还需要能够访问其他键,而不仅仅是我想要修改的键。

Your current problem is about conflicting mutable and immutable borrows, but there's a deeper problem here.您当前的问题是关于可变借用和不可变借用的冲突,但这里有一个更深层次的问题。 This data structure cannot work for what you're trying to do:此数据结构不能用于您要执行的操作:

struct MyStruct<'a> {
    value: i32,
    substructs: Option<Vec<Pin<&'a MyStruct<'a>>>>,
}

struct Toplevel<'a> {
    my_structs: HashMap<String, Pin<Box<MyStruct<'a>>>>,
}

Any time a type has a lifetime parameter, that lifetime necessarily outlives (or lives exactly as long as) the values of that type.任何时候一个类型有一个生命周期参数,这个生命周期必然比该类型的值更长寿(或完全一样)。 A container Toplevel<'a> which contains references &'a MyStruct must refer to MyStruct s which were created before the Toplevel — unless you're using special tools like an arena allocator .包含引用&'a MyStruct的容器Toplevel<'a>必须引用Toplevel之前创建的MyStruct s - 除非您使用特殊工具,如arena allocator

(It's possible to straightforwardly build a tree of references, but they must be constructed leaves first and not using a recursive algorithm; this is usually impractical for dynamic input data.) (直接构建引用树是可能的,但它们必须首先构造叶子而不是使用递归算法;这对于动态输入数据通常是不切实际的。)

In general, references are not really suitable for creating data structures;一般来说,引用并不真正适合创建数据结构; rather they're for temporarily “borrowing” parts of data structures.相反,它们用于临时“借用”数据结构的一部分。

In your case, if you want to have a collection of all the MyStructs and also be able to add connections between them after they are created, you need both shared ownership and interior mutability:在您的情况下,如果您想要拥有所有MyStructs的集合,并且还能够它们创建后在它们之间添加连接,您需要共享所有权和内部可变性:

use std::collections::HashMap;
use std::cell::RefCell;
use std::rc::Rc;

struct MyStruct {
    value: i32,
    substructs: Option<Vec<Rc<RefCell<MyStruct>>>>,
}

struct Toplevel {
    my_structs: HashMap<String, Rc<RefCell<MyStruct>>>,
}

The shared ownership via Rc allows both Toplevel and any number of MyStruct s to refer to other MyStruct s.通过Rc的共享所有权允许Toplevel和任意数量的MyStruct s 引用其他MyStruct s。 The interior mutability via RefCell allows the MyStruct 's substructs field to be modified even while it's being referred to from other elements of the overall data structure.通过RefCell的内部可变性允许修改MyStructsubstructs字段,即使它被整个数据结构的其他元素引用。

Given these definitions, you can write the code that you wanted:根据这些定义,您可以编写所需的代码:

fn main() {
    let mut toplevel = Toplevel {
        my_structs: HashMap::new(),
    };

    // First pass: add the elements to the HashMap
    toplevel.my_structs.insert(
        "abc".into(),
        Rc::new(RefCell::new(MyStruct {
            value: 0,
            substructs: None,
        })),
    );
    toplevel.my_structs.insert(
        "def".into(),
        Rc::new(RefCell::new(MyStruct {
            value: 5,
            substructs: None,
        })),
    );
    toplevel.my_structs.insert(
        "ghi".into(),
        Rc::new(RefCell::new(MyStruct {
            value: -7,
            substructs: None,
        })),
    );

    // Second pass: for each MyStruct, add substructs
    let subs = vec![
        toplevel.my_structs["abc"].clone(),
        toplevel.my_structs["def"].clone(),
        toplevel.my_structs["ghi"].clone(),
    ];
    toplevel.my_structs["abc"].borrow_mut().substructs = Some(subs);
}

Note that because you're having "abc" refer to itself, this creates a reference cycle, which will not be freed when the Toplevel is dropped.请注意,因为您有"abc"引用自身,这会创建一个引用循环,当Toplevel被删除时,它不会被释放。 To fix this, you can impl Drop for Toplevel and explicitly remove all the substructs references.要解决此问题,您可以impl Drop for Toplevel并显式删除所有substructs引用。


Another option, arguably more 'Rusty' is to just use indices for cross-references .另一种选择,可以说更“生锈”的是只使用索引进行交叉引用 This has several pros and cons:这有几个优点和缺点:

  • Adds the cost of additional hash lookups.增加额外哈希查找的成本。
  • Removes the cost of reference counting and interior mutability.消除了引用计数和内部可变性的成本。
  • Can have “dangling references”: a key could be removed from the map, invalidating the references to it.可以有“悬空引用”:可以从映射中删除一个键,使对它的引用无效。
use std::collections::HashMap;

struct MyStruct {
    value: i32,
    substructs: Option<Vec<String>>,
}

struct Toplevel {
    my_structs: HashMap<String, MyStruct>,
}

fn main() {
    let mut toplevel = Toplevel {
        my_structs: HashMap::new(),
    };

    // First pass: add the elements to the HashMap
    toplevel.my_structs.insert(
        "abc".into(),
        MyStruct {
            value: 0,
            substructs: None,
        },
    );
    toplevel.my_structs.insert(
        "def".into(),
        MyStruct {
            value: 5,
            substructs: None,
        },
    );
    toplevel.my_structs.insert(
        "ghi".into(),
        MyStruct {
            value: -7,
            substructs: None,
        },
    );

    // Second pass: for each MyStruct, add substructs
    toplevel.my_structs.get_mut("abc").unwrap().substructs =
        Some(vec!["abc".into(), "def".into(), "ghi".into()]);
}

In your code, you are attempting to modify a value referenced in the vector as immutable, which is not allowed.在您的代码中,您试图将向量中引用的值修改为不可变的,这是不允许的。 You could store mutable references in the vector instead, and mutate them directly, like this:您可以将可变引用存储在向量中,然后直接改变它们,如下所示:

let subs = vec![
    toplevel.my_structs.get_mut("abc").unwrap(),
    toplevel.my_structs.get_mut("def").unwrap(),
    toplevel.my_structs.get_mut("ghi").unwrap(),
];
(*subs[0]).substructs = Some(subs.clone());

However, it's easier (although more expensive) to store clones of the structs instead of references:但是,存储结构的克隆而不是引用更容易(虽然更昂贵):

let subs = vec![
    toplevel.my_structs.get("abc").unwrap().clone(),
    toplevel.my_structs.get("def").unwrap().clone(),
    toplevel.my_structs.get("ghi").unwrap().clone(),
];
(*toplevel.my_structs.get_mut("abc").unwrap()).substructs = Some(subs);

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

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