Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

rust

I have a collection of Foo.

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

I want a HashMap which has the key &foo.k and the value foo.

Apparently, it is not possible without redesigning Foo by introducing Rc or clone/copy the 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 by 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 tedious. What if a Foo has multiple fields and different collections of Foo to key on different fields?

like image 296
colinfang Avatar asked Apr 29 '17 12:04

colinfang


People also ask

What is a lookup table called?

1. In computer programming, a lookup table, also known as a LUT, is an array that holds values which would otherwise need to be calculated. The table may be manually populated when the program is written, or the program may populate the table with values as it calculates them.

What is lookup table with example?

In data analysis applications, such as image processing, a lookup table (LUT) is used to transform the input data into a more desirable output format. For example, a grayscale picture of the planet Saturn will be transformed into a color image to emphasize the differences in its rings.


2 Answers

Apparently, it is not possible without redesigning Foo by introducing Rc or clone/copy the k.

That's correct, it is not possible to have HashMap<&K, V> where the key points to some component of the value.

The HashMap owns the key and the value, conceptually storing both in big vectors. When a new value is added to the HashMap, these existing values might need to be moved around due to hash collisions or the vectors might need to be reallocated to hold more items. Both of these operations would invalidate the address of any existing key, leaving it pointing at invalid memory. This would break Rust's safety guarantees, thus it is disallowed.

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

Additionally, trentcl points out that HashMap::get_mut would allow you to get a mutable reference to the key, which would allow you to change the key without the map knowing. As the documentation states:

It is a logic error for a key to be modified in such a way that the key's hash, as determined by the Hash trait, or its equality, as determined by the Eq trait, changes while it is in the map.


Workarounds include:

  • Remove the key from the struct and store it separately. Instead of HashMap<&K, V> where V is (K, Data), store HashMap<K, Data>. You can return a struct which glues references to the key and value together (example)

  • Share ownership of the key using Rc (example)

  • Create duplicate keys using Clone or Copy.

  • Use a HashSet as you have done, enhanced by Sebastian Redl's suggestion. A HashSet<K> is actually just a HashMap<K, ()>, so this works by transferring all ownership to the key.

like image 156
Shepmaster Avatar answered Oct 05 '22 02:10

Shepmaster


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

struct FooByK(Foo);

Then implement the various traits needed for the set for this struct instead. This lets you choose a different wrapper type if you need a set that indexes by a different member.

like image 43
Sebastian Redl Avatar answered Oct 05 '22 02:10

Sebastian Redl