Ólavur Nón
Ólavur Nón

Reputation: 395

"Converting" unordered data structure to 2D HashMap

I have this datastructure in Rust:

objects: [Object { ob: 1, id: 0 }, 
          Object { ob: 1, id: 1 }, 
          Object { ob: 0, id: 3 }, 
          Object { ob: 0, id: 2 },
          ...
         ]

I'm using a HashMap where i can use the two keys "ob" and "id" to reference to data that im gonna store in this 2D HashMap. To create a 2D HashMap, i'm using the code found in this answer: How to implement HashMap with two keys?.

In the code below, i'm trying to firstly create all the structs (aCustomStructOfMine), and store them in the table with their belonging two keys, "ob" and "id". Then later in the code (not written yet), i will be accessing the structs iteratively and will be editing them and using their at-the-moment stored data.

fn analyse(objects: Vec<Object>) {
    let mut table = Table::new();
    for object in objects {
        let ob = &object.ob;
        let id = &object.id;
        table.set(A(ob), B(id), aCustomStructOfMine::new());
    }
}

I'm getting the following error:

borrowed value does not live long enough
assignment requires that `object.id` is borrowed for `'static`

How can i achieve the desired 2D HashMap from the given objects variable.

I'm new to rust, and although i understand the basic principles of ownership, borrowing, lifetime and more, it's really hard to actually code with these concepts in mind.

Upvotes: 0

Views: 88

Answers (1)

Finomnis
Finomnis

Reputation: 22728

I personally think you are getting confused by the other post and make this way too complicated.

If two types KeyA and KeyB implement Hash, then the tuple (KeyA, KeyB) automatically does so as well. There is no need to create a fancy new Table type that can take two keys. Just use a tuple.

There are some other remarks concerning ownership I'd like to make, but I think they are best annotated in the source code. Here is a working version of what you intended, I think:

use std::collections::HashMap;

// This is just for demonstration. Use your own types.
// I used `String` because it already implements `Eq` and `Hash`.
type KeyA = String;
type KeyB = String;

// This is an example type that you should replace with your own
// data type in the HashMap
pub struct MyData {}

// Some generic input type, to stick as close to your example as possible
pub struct Object {
    a: KeyA,
    b: KeyB,
}

// The analyze function, as in your example.
// Note that we take `objects` by value, not by reference.
// That means we own it and can do whatever we want with it.
// This is important because you never mentioned that the Keys are supposed to be
// `Clone` or `Copy`.
pub fn analyse(objects: Vec<Object>) -> HashMap<(KeyA, KeyB), MyData> {
    // No need to use fancy new types for your table.
    // Use use a tuple (KeyA, KeyB) as the key.
    // If both KeyA and KeyB implement `Hash`, then the
    // tuple implements it as automatically well.
    let mut table = HashMap::new();

    // Note that I wrote `in objects` and not `in &objects`, meaning this loop actually
    // consumes and destroys `objects`, extracts `object` out of it and gives you a fully owned
    // `object`.
    for object in objects {
        // This again is not a borrow, but a move.
        // The `object.a` gets moved out of `object` and into `key_a`.
        // No need to reference here, `object` doesn't get used any further
        // anyway. And for inserting it into a `HashMap`, we need owned keys anyway.
        //
        // I think this was the main point you were struggling with in the original code,
        // that you used references here.
        let key_a = object.a;
        let key_b = object.b;

        // Just create the key tuple and insert
        table.insert((key_a, key_b), MyData {});
    }

    // Return the table
    table
}

Upvotes: 0

Related Questions