tomage
tomage

Reputation: 25

Lifetime problem with HashMap with HashMap references as values

(New to rust, FYI!)

So - I've been trying to grok the concept of lifetimes in Rust. I've read the documentation, and read some blogs and SO posts on the topic. But still not quite getting it (hence, possibly a bad title to the question).

I've got a particular problem I'm trying to figure out, which I've boiled down to this small sample code (tried to make as close to a working sample as I could):

use std::collections::HashMap;

// Imagine this is actually a more complex type:
type BigComplexType = i32;

fn some_expensive_computation() -> BigComplexType {
    // Imagine this takes some arguments, and takes a long time to run
    return 123;
}

fn construct() -> HashMap<i32, &'static HashMap<i32, BigComplexType>> {
    let mut main = HashMap::new();

    let mut nested = HashMap::new();
    nested.insert(1111, some_expensive_computation());
    nested.insert(2222, some_expensive_computation());
    // ... and lots more inserts...

    main.insert(10, &nested);
    main.insert(20, &nested);
    // ... and lots more inserts...

    // Imagine a lot more other nested HashMaps to follow here
    // let mut nested2 = ...
    // ...
    // main.insert(..., &nested2);
    // ...

    return main;
}


fn main() {
    construct();
}

This example is a bit trivial - in the actual code, I'm creating much more complicated and deeper structures in the construct() function.

What I'm trying to do is to create some sort of cache that holds these pre-computed values, so that they can be easily and quickly accessed else in the code. Perhaps this can all just be done in some totally different way - but I figured there must be a way to do it this way.

But, rustc complains here, because nested only exists in construct(), and once we're out of the function, it ceases to exist, and thus all references are invalid (or, that's how I understand the problem).

I've tried to introduce a 'a lifetime to the construct() function, and use that lifetime on the nested HashMap below but no dice. Got some errors, and could never fully make it work. I've tried all sorts of variants of adding lifetime annotations, but no dice.

I have a feeling I'm just not grokking some aspect of the whole concept here. Can anyone help point me in the right direction?

(I did think about gathering the nested HashMap into a vector and returning along side the main hashmap - so that then the function is returning the main HashMap, plus a vector of the nested ones - thus, the lifetime would be guaranteed, I think - but I hit some other roadblocks trying that, and have a feeling I'm over-complicating things.)

For reference, this is the error I get in compiling the above:

error[E0515]: cannot return value referencing local variable `nested`
  --> lifetime_return.rs:29:12
   |
19 |     main.insert(10, &nested);
   |                     ------- `nested` is borrowed here
...
29 |     return main;
   |            ^^^^ returns a value referencing data owned by the current function

error[E0515]: cannot return value referencing local variable `nested`
  --> lifetime_return.rs:29:12
   |
20 |     main.insert(20, &nested);
   |                     ------- `nested` is borrowed here
...
29 |     return main;
   |            ^^^^ returns a value referencing data owned by the current function

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0515`.

Any/all help would be greatly appreciated! I tried to look for similar question, but couldn't spot one - perhaps I just didn't recognize some SO post as a duplicate, as I don't fully understand the lifetime model just yet.

Upvotes: 1

Views: 618

Answers (1)

Aplet123
Aplet123

Reputation: 35502

Your intuition for why this doesn't work is correct: nested lives only inside construct, and you try to return references to it in the hashmap that live for longer than the function. Assuming that you don't want to clone the nested maps, presumably because they're very large, you can use Rc instead as a way to have trivially cloneable references to the nested maps that keep them alive for as long as necessary:

use std::collections::HashMap;
use std::rc::Rc;

type BigComplexType = i32;

fn some_expensive_computation() -> BigComplexType {
    return 123;
}

fn construct() -> HashMap<i32, Rc<HashMap<i32, BigComplexType>>> {
    let mut main = HashMap::new();

    let mut nested = HashMap::new();
    nested.insert(1111, some_expensive_computation());
    nested.insert(2222, some_expensive_computation());

    let nested_rc = Rc::new(nested);
    main.insert(10, Rc::clone(&nested_rc));
    main.insert(20, nested_rc); // can move the Rc for the last insert
    
    let mut nested2 = HashMap::new();
    nested2.insert(3333, some_expensive_computation());
    nested2.insert(4444, some_expensive_computation());
    
    let nested2_rc = Rc::new(nested2);
    main.insert(30, Rc::clone(&nested2_rc));
    main.insert(40, nested2_rc);

    return main;
}


fn main() {
    let map = construct();
    println!("{}", map[&10][&1111]); // 123
}

Playground link

Upvotes: 1

Related Questions