我有一个函数f
接受两个引用,一个mut
和一个不接受mut
。我有f
a 内的值HashMap
:
use std::collections::HashMap;
fn f(a: &i32, b: &mut i32) {}
fn main() {
let mut map = HashMap::new();
map.insert("1", 1);
map.insert("2", 2);
{
let a: &i32 = map.get("1").unwrap();
println!("a: {}", a);
let b: &mut i32 = map.get_mut("2").unwrap();
println!("b: {}", b);
*b = 5;
}
println!("Results: {:?}", map)
}
Run Code Online (Sandbox Code Playgroud)
这不起作用,因为HashMap::get
并HashMap::get_mut
尝试同时可变借和不变借:
use std::collections::HashMap;
fn f(a: &i32, b: &mut i32) {}
fn main() {
let mut map = HashMap::new();
map.insert("1", 1);
map.insert("2", 2);
{
let a: &i32 = map.get("1").unwrap();
println!("a: {}", a);
let b: &mut i32 = map.get_mut("2").unwrap();
println!("b: {}", b);
*b = 5;
}
println!("Results: {:?}", map)
}
Run Code Online (Sandbox Code Playgroud)
在我的真实代码中,我使用了一个大而复杂的结构,而不是一个,i32
因此克隆它不是一个好主意。
事实上,我正在可变/不变地借用两种不同的东西,例如:
struct HashMap {
a: i32,
b: i32,
}
let mut map = HashMap { a: 1, b: 2 };
let a = &map.a;
let b = &mut map.b;
Run Code Online (Sandbox Code Playgroud)
有没有办法向编译器解释这实际上是安全的代码?
我看到在具体情况下如何解决iter_mut
:
{
let mut a: &i32 = unsafe { mem::uninitialized() };
let mut b: &mut i32 = unsafe { mem::uninitialized() };
for (k, mut v) in &mut map {
match *k {
"1" => {
a = v;
}
"2" => {
b = v;
}
_ => {}
}
}
f(a, b);
}
Run Code Online (Sandbox Code Playgroud)
但这与 HashMap::get/get_mut
TL;DR:您将需要更改类型 HashMap
使用方法时,编译器不会检查方法的内部,也不会执行任何运行时模拟:它仅将其所有权/借用检查分析基于方法的签名。
就您而言,这意味着:
get
就会借用整个HashMap
,get_mut
就会可变地借用整个HashMap
。因此,使用 a 不可能同时HashMap<K, V>
获得 a&V
和&mut V
。
因此,解决方法是避免需要&mut V
完全。
这可以通过使用Cell
或来完成RefCell
:
HashMap
变成HashMap<K, RefCell<V>>
,get
在这两种情况下使用,borrow()
获得的参考和borrow_mut()
得到一个可变引用。use std::{cell::RefCell, collections::HashMap};
fn main() {
let mut map = HashMap::new();
map.insert("1", RefCell::new(1));
map.insert("2", RefCell::new(2));
{
let a = map.get("1").unwrap();
println!("a: {}", a.borrow());
let b = map.get("2").unwrap();
println!("b: {}", b.borrow());
*b.borrow_mut() = 5;
}
println!("Results: {:?}", map);
}
Run Code Online (Sandbox Code Playgroud)
这将在您每次调用borrow()
or时添加运行时检查borrow_mut()
,并且如果您尝试不正确地使用它们(如果两个键相等,与您的期望不同),则会发生恐慌。
至于使用字段:这是有效的,因为编译器可以根据每个字段推断借用状态。
自从提出问题以来,事情似乎发生了变化。在 Rust 1.38.0(可能更早)中,以下代码可以编译并运行:
use std::collections::HashMap;
fn f(a: &i32, b: &mut i32) {}
fn main() {
let mut map = HashMap::new();
map.insert("1", 1);
map.insert("2", 2);
let a: &i32 = map.get("1").unwrap();
println!("a: {}", a);
let b: &mut i32 = map.get_mut("2").unwrap();
println!("b: {}", b);
*b = 5;
println!("Results: {:?}", map)
}
Run Code Online (Sandbox Code Playgroud)
不需要RefCell
,甚至也不需要内部作用域。