Lifetimes - Storing a Reference of an Object in a HashMap

I'm trying to store a reference of an object which is hold in a HashMap in a Vec in order to access it in its order of insertion:
Reference from a HashMap in a Vec

use std::collections::HashMap;
use std::vec::Vec;

#[derive(Debug)]
struct Data {
    id: u32,
    name: String,
}

struct DataFactory<'fct> {
    vdatas: Vec<&'fct Data>,
    lstdatas: HashMap<u32, Data>,
}

impl<'fct> DataFactory<'fct> {
    fn add_data(&'fct mut self, inputdata: Data) -> Option<&mut Data> {
        let idtaid = inputdata.id;

        self.lstdatas.insert(inputdata.id, inputdata);

        let odta = self.lstdatas.get_mut(&idtaid);

        match &odta {
            Some(dta) => self.vdatas.push(dta),
            None => {
                eprintln!("data set (id: '{}'): add Data failed!", &idtaid);
            }
        }

        odta
    }
}

fn main() {
    let mut dfact = DataFactory {
        vdatas: Vec::new(),
        lstdatas: HashMap::new(),
    };
    let dset = Data {
        id: 11,
        name: "dataset 11".to_string(),
    };

    let odset = dfact.add_data(dset);

    println!("added set: '{:?}'", odset);
}

but I'm getting constant compiler errors

Compiling playground v0.0.1 (/playground)
error[E0597]: `odta` does not live long enough
  --> src/main.rs:23:15
   |
15 | impl<'fct> DataFactory<'fct> {
   |      ---- lifetime `'fct` defined here
...
23 |         match &odta {
   |               ^^^^^ borrowed value does not live long enough
24 |             Some(dta) => self.vdatas.push(dta),
   |                  --- assignment requires that `odta` is borrowed for `'fct`
...
31 |     }
   |     - `odta` dropped here while still borrowed

error[E0505]: cannot move out of `odta` because it is borrowed
  --> src/main.rs:30:9
   |
15 | impl<'fct> DataFactory<'fct> {
   |      ---- lifetime `'fct` defined here
...
23 |         match &odta {
   |               ----- borrow of `odta` occurs here
24 |             Some(dta) => self.vdatas.push(dta),
   |                  --- assignment requires that `odta` is borrowed for `'fct`
...
30 |         odta
   |         ^^^^ move out of `odta` occurs here

Some errors have detailed explanations: E0505, E0597.
For more information about an error, try `rustc --explain E0505`.
error: could not compile `playground` due to 2 previous errors 

and I'm running in cycles with it.

I read lots of documentation about it but all examples and explanations are just too simple or too specific as to be reusable or applicable for this use case.

So, please if anyone knew how to write this correctly or about any documentation that comes close to it it would help me a lot.

The variable odta is created in your add_data() function, and is gone after the function returns. This means that it's impossible to store its address in a data structure that lives longer than that particular function's scope.

I'm not sure why you are taking the address of the returned Option. If you remove the & from match &odata, then in the Some(dta) pattern, you will get the actual &mut Data, which then you could store in self.vdatas.

However, in that case, it will be impossible to simultaneously return it. Two mutable references or a mutable and an immutable reference to the same value are prohibited.


Typically, doing this kind of indirect "self-referential" (not quite) data structure is an anti-pattern. If you tell us what the actual use case is instead of the toy example above, we could help better to find a different solution to the problem you are having.

The Use Case is quite as simple as common to access stored data by its insertion order and by its name.

I need to know that object dset11 arrived before dset9.
Then on one place of the application I can just iterate over DataFactory::vdatas and at other places of the application I can look the data up by its Data::id.

I refactured the code as you suggested and the error changed just as you mentions to cannot borrow dfact as mutable more than once at a time:
refactured reference from a HashMap object

use std::collections::HashMap;
use std::vec::Vec;

#[derive(Debug)]
struct Data {
    id: u32,
    name: String,
}

struct DataFactory<'fct> {
    vdatas: Vec<&'fct Data>,
    lstdatas: HashMap<u32, Data>,
}

impl<'fct> DataFactory<'fct> {
    fn add_data(&'fct mut self, inputdata: Data) {
        let idtaid = inputdata.id;

        self.lstdatas.insert(inputdata.id, inputdata);

        let odta = self.lstdatas.get(&idtaid);

        match odta {
            Some(dta) => self.vdatas.push(dta),
            None => {
                eprintln!("data set (id: '{}'): add Data failed!", &idtaid);
            }
        }
    }

    fn get_data(&'fct mut self, idataid: &u32) -> Option<&mut Data> {
        self.lstdatas.get_mut(idataid)
    }
}

#[allow(unused_variables)]
fn main() {
    let mut dfact = DataFactory {
        vdatas: Vec::new(),
        lstdatas: HashMap::new(),
    };
    let dset11 = Data {
        id: 11,
        name: "dataset 11".to_string(),
    };
    let dset9 = Data {
        id: 9,
        name: "dataset 9".to_string(),
    };

    let idsetid11 = dset11.id;
    let idsetid9 = dset9.id;

    dfact.add_data(dset11);
    dfact.add_data(dset9);

    let mut odset11 = dfact.get_data(&idsetid11);

    println!("added set 0: '{:?}'", odset11);

    match &mut odset11 {
        Some(dset) => {
            dset.name = String::from("changed dataset 11");
        }
        None => {
            eprintln!("failed to add dataset id: '{}'", &idsetid11);
        }
    }

    println!("added set 1: '{:?}'", odset11);
}

which then produces the error:

Compiling playground v0.0.1 (/playground)
error[E0499]: cannot borrow `dfact` as mutable more than once at a time
  --> src/main.rs:55:5
   |
54 |     dfact.add_data(dset11);
   |     ---------------------- first mutable borrow occurs here
55 |     dfact.add_data(dset9);
   |     ^^^^^^^^^^^^^^^^^^^^^
   |     |
   |     second mutable borrow occurs here
   |     first borrow later used here

error[E0499]: cannot borrow `dfact` as mutable more than once at a time
  --> src/main.rs:57:23
   |
54 |     dfact.add_data(dset11);
   |     ---------------------- first mutable borrow occurs here
...
57 |     let mut odset11 = dfact.get_data(&idsetid11);
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^
   |                       |
   |                       second mutable borrow occurs here
   |                       first borrow later used here

For more information about this error, try `rustc --explain E0499`.
error: could not compile `playground` due to 2 previous errors

I was already thinking of indirect reference storing the Data::id but this then would require additional lookups on each iteration over DataFactory::vdatas.

In this case, I would collect the data in a simple Vec by-value, and I wouldn't store any references. I would then store indices pointing into that vector inside the HashMap, like this.


I don't understand why you are borrowing variables superfluously all the time; it looks like you are confusing patterns with expressions. This is not how patterns work.

I also changed the names of fields and variables. Please don't use either Hungarian notation (it's useless in a statically typed language) or Python-esque long words without separators, they are hard to read and un-idiomatic in Rust. I added a #[derive(Default)] for DataSet too, so you can avoid the long-winded initialization syntax.

Thank you for the refacturing of my code.

I especially enjoyed the simplified creation with let mut dfact = DataFactory::default(); with #[derive(Default)
and also the match construct:

    match odset11 {
        Some(ref mut dset) => {
            dset.name = String::from("changed dataset 11");
        }
        None => {
            eprintln!("failed to add dataset id: '{}'", idsetid11);
        }
    }

Actually constructions like this have not been part of the Rust Book when I started learning it.
Now coming back at it I see that it has greatly improved:
match on references and pointers

About the Hungarian Notation I'm not sure if it is all useless since when you look at a line like:

let mut d = f.get_data(i);

you cannot tell which type d is of since Rust calculates the correct type at compilation time and does not require an explicit declaration.
Even more during the life time of a single function d can be redeclared with different types like in:

#[allow(unused_variables)]
fn main() {
    let mut f = DataFactory::default();
    let d = Data {
        id: 11,
        name: "dataset 11".to_string(),
    };
    let dset9 = Data {
        id: 9,
        name: "dataset 9".to_string(),
    };

    let idsetid11 = d.id;
    let i = dset9.id;

    println!("created set 0: '{:?}'", d);

    f.add_data(d);
    f.add_data(dset9);

    let mut d = f.get_data(i);

    println!("added set 1: '{:?}'", d);

    match d {
        Some(ref mut d) => {
            println!("matched set 1: '{:?}'", d);
            d.name = String::from("changed dataset 9");
        }
        None => {
            eprintln!("failed to add dataset id: '{}'", i);
        }
    }

    println!("added set 2: '{:?}'", d);
}

which prints:

created set 0: 'Data { id: 11, name: "dataset 11" }'
added set 1: 'Some(Data { id: 9, name: "dataset 9" })'
matched set 1: 'Data { id: 9, name: "dataset 9" }'
added set 2: 'Some(Data { id: 9, name: "changed dataset 9" })'

here we have 3 different d:

  • d as Data { id: 11 }
  • d as Option<&mut Data>
  • d as &mut Data { id: 9 }

I personally think on big projects this style can obfuscate the code.

Then why don't you just… write the type annotation proper, if you want it? Surely if you keep using Hungarian notation instead of real type annotations, you are going to run into great backpressure.

You got a good point :slight_smile: there with writing the long declaration as:

let mut d: Option<&mut Data> = f.get_data(i);

Of course when sharing code with other developers using a common style convention reduces unnecessary friction.
I think I still have to work on this one.

Worrying about shadowing seems to be relatively common when starting out with Rust; I did too. I think most people eventually end up feeling that the type checker protects you from most bugs that it could cause. You can't just use an Option<T> as if it were a T, and so on. That said, clippy provides several optional lints you can turn on to warn or error on shadowing in some or all cases: Clippy Lints

2 Likes

Thank you for the hint with the Clippy Lints.

Of course the compiler will check the code and not allow incorrect usage of variables according to their real type at compile time.
My concern is more about readability and productivity. When you need to work with foreign code it helps to be more productive when you don't need to debug the code to find out which type a variable actually has.

1 Like

For reasons of readability and productivity I urge all Rust developers to follow the Rust case conventions for identifiers and code formatting. As told by 'cargo clippy' and 'cargo fmt' out of the box.

If you use an editor like VSCode with the rust-analyser plug-in it will show you the types when you open Rust source files. No need for the ugly warts of Hungarian notation.

1 Like