Below is a miniaturized example of my error. Imagine f1 and f2 are library calls you cannot change, what is the best way to convert from immutable to mutable reference
use std::collections::HashMap;
fn main() {
let key1 = String::from("key1");
let key2 = String::from("key2");
let m = f1(&key1, &key2); // imagine f1 is an api call and cannot be changed by you
for (k,v) in m.into_iter() {
f2(k); // f2 is another api call
}
}
fn f1<'a>(key1: &'a String, key2:&'a String) -> HashMap<&'a String, i32> {
let mut m: HashMap<&String, i32> = HashMap::new();
m.insert(key1, 99);
m.insert(key2, 23);
m
}
fn f2(s:&mut String) {
println!("got key {}", s);
}
Error is
error[E0308]: mismatched types
--> src/main.rs:8:12
|
8 | f2(k); // f2 is another api call
| ^ types differ in mutability
|
= note: expected mutable reference &mut std::string::String
found reference &std::string::String
There isn't really a way to do this, since the keys of the hashmap are only immutable references, which means you probably aren't expected to be able to mutate them. The fact that references to Strings are being used as hashmap keys at all is a little suspicious, so you may need to give more information on what you're trying to do here.
To put it more strongly, there is no sound way to convert &T to &mut T, even withunsafe. You're going to have to provide more information in order to get better help, because this is an XY problem.
It looks like KVal has a KVal::String variant which takes in a &str, which looks like exactly what you need. &String automatically coerces to &str via deref coercions.
BTW, &String type shouldn't be used, especially as a function argument. Owned String already passes data by reference. &String is a double indirection (a temporary borrow of a growable string, but made read-only so you get worst of both worlds). For arguments you should use &str (string view).
After something is put in a HashMap, mutating it very problematic — if the hash of the mutated object changes, the HashMap won't find it! So it's not just the borrow checker being annoying, it's a logic error waiting to happen.
You probably should put String in the HashMap (or Box<str>, slightly more efficient, slightly more annoying to use). Temporary references in data structures are useful in 5% cases for real, and in 95% other cases are a novice mistake caused by misunderstanding ownership and confusing Rust references with storing "by reference" from other languages.
If you really need strings stored by reference in multiple places, you can use a shared owned reference Arc<String> for the keys, and then use Arc::make_mut to mutate a copy.
All of that makes perfect sense, and in my own code I would probably never use &String, as I mentioned the problem arise from passing data between two different libraries. I agree both the libraries are correct in their own individual way, HaspMap keys should never be mutable.
Looks like the only solution here is to get all the keys from the first map into a temporary vector (having the same lifetime) and then use that, will make my code slightly messy but have to live with it.
PS: Coming from kdb/q background I always look to save few key strokes whereever I can.
How about cloning the strings after you take them out of the HashMap? It's regrettable, but I don't think you can avoid it.
fn main() {
let key1 = String::from("key1");
let key2 = String::from("key2");
let m = f1(&key1, &key2); // imagine f1 is an api call and cannot be changed by you
for (k,v) in m.into_iter() {
let mut k = k.clone();
f2(&mut k); // f2 is another api call
}
}
If you need the strings to have a longer lifetime, you could also stick em in a vec or something with all owned strings rather than borrowed:
fn main() {
let key1 = String::from("key1");
let key2 = String::from("key2");
let m = f1(&key1, &key2); // imagine f1 is an api call and cannot be changed by you
let mut m: Vec<(String, i32)> = m.iter().map(|(&k, &v)| (k.clone(), v)).collect();
for (k,v) in &mut m {
f2(k); // f2 is another api call
}
}