Immutable to mutable reference

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

error: aborting due to previous error

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.

1 Like

To put it more strongly, there is no sound way to convert &T to &mut T, even with unsafe. You're going to have to provide more information in order to get better help, because this is an XY problem.

4 Likes

https://doc.rust-lang.org/nomicon/transmutes.html

  • Transmuting an & to &mut is UB
    • Transmuting an & to &mut is always UB
    • No you can't do it
    • No you're not special

And it's equally UB if you try this via pointer casts or the like.

3 Likes

Fair Enough.
I am using avro-rs library and trying to match on various values of this enum https://github.com/flavray/avro-rs/blob/master/src/types.rs#L74
when i deconstruct map I get HashMap(&String, &mut Value), subsequently I have to pass on the key to this enum https://github.com/redsift/rkdb/blob/master/src/kbindings.rs#L74

Why do you need to do this?

For converting the keys in the avro map field from Value to KVal, essentially I have to convert from avro-rust types to kdb-KVal types.

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.

1 Like

I Know that, but I have to use KVal::Symbol, keys are semantically symbols not strings

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.

3 Likes

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

Thanks for the suggestions, 2) works perfect for my use case.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.