What's the idiomatic way to make a lookup table that uses an item field as a key?

I have a collection Foo

.

struct Foo {
    k: String,
    v: String,
}

      

I want a HashMap

that has a key &foo.k

and a value Foo

.

Apparently this is not possible without reorientation Foo

by injection Rc

or cloning / copying k

.

fn t1() {
    let foo = Foo { k: "k".to_string(), v: "v".to_string() };
    let mut a: HashMap<&str, Foo> = HashMap::new();
    a.insert(&foo.k, foo); // Error
}

      

There seems to be a workaround, abusing get()

from HashSet

( Playground ):

use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher, BuildHasher};
use std::collections::hash_map::Entry::*;

struct Foo {
    k: String,
    v: String,
}

impl PartialEq for Foo {
    fn eq(&self, other: &Self) -> bool { self.k == other.k }
}

impl Eq for Foo {}

impl Hash for Foo {
    fn hash<H: Hasher>(&self, h: &mut H) { self.k.hash(h); }
}

impl ::std::borrow::Borrow<str> for Foo {
    fn borrow(&self) -> &str {
        self.k.as_str()
    }
}

fn t2() {
    let foo = Foo { k: "k".to_string(), v: "v".to_string() };
    let mut a: HashSet<Foo> = HashSet::new();
    a.insert(foo);
    let bar = Foo { k: "k".to_string(), v: "v".to_string() };
    let foo = a.get("k").unwrap();
    println!("{}", foo.v);
}

      

This is pretty tiresome. What if you Foo

have multiple fields and different collections Foo

to enter in different fields?

+3


source to share


2 answers


Apparently this is not possible without reorientation Foo

by injection Rc

or cloning / copying k

.

That's right, it's impossible to have HashMap<&K, V>

where the key points to some component of the value.

HashMap

owns the key and value, conceptually storing both in large vectors. When a new value is added to HashMap

, these existing values ​​may need to be moved due to hash collisions, or the vectors may need to be reallocated to store more elements. Both of these operations are invalid for the address of any existing key, causing it to point to invalid memory. This would violate Rust's security guarantees, so it is prohibited.

Read on Why can't I store a value and a reference to that value in the same structure? for a detailed discussion.

Also, trentcl specifies that it will allow you to get a mutable reference to the key, which will allow you to change the key without knowing the map. As the documentation states: HashMap::get_mut



It is a logical error for the key to be modified in such a way that the hash of the key, defined by the Hash trait, or its equality, defined by the trait Eq, changes when it is on the map.


Workarounds:

  • Remove the key from the structure and save it separately. Instead of HashMap<&K, V>

    where V is (K, Data)

    , save HashMap<K, Data>

    . You can return a structure that glues key and value references together ( example )

  • Transfer ownership of the key using Rc

    ( example )

  • Create duplicate keys with Clone

    or Copy

    .

  • Use HashSet

    , as you did, Sebastian Redl's improved suggestion . A HashSet<K>

    is actually just a HashMap<K, ()>

    , so this works by transferring all ownership of the key.

+4


source


You can enter a wrapper type for the item stored in the set.

struct FooByK(Foo);

      



We then implement the various traits required to set up this structure. This allows you to choose a different type of wrapper if you need a collection that indexes a different member.

+1


source







All Articles