1

I'm trying to create a simple collection:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=361258962c9a25b953aab2a9e4999cc9

use std::collections::HashMap;

pub struct User {
    pub id: u32,
    pub name: String,
}

pub struct UsersIndex<'a> {
    list: Vec<User>,
    index: HashMap<u32, &'a User>,
}

impl UsersIndex<'_> {
    pub fn new() -> Self {
        UsersIndex {
            list: Vec::new(),
            index: HashMap::new(),
        }
    }

    pub fn add(&mut self, user: User) {
        self.list.push(user);
        self.index.insert(user.id, &user);
    }

    pub fn get(&self, id: u32) -> Option<&&User> {
        self.index.get(&id)
    }
}

but can not fix the errors:

use of moved value: user

user does not live long enough

As I understand I have to take ownership of User, but google doesn't help me how to do it. Rust says that I need to implement the Copy trait, but User contains a field of type String.

Román Cárdenas
  • 492
  • 5
  • 15
broadsw0rd
  • 15
  • 5
  • Self-referential structs like you're trying to implement here are quite tricky to do in Rust and usually require `unsafe`. If I were you, I'd try to find a different solution for this problem. – isaactfa Jul 07 '21 at 08:15
  • See also this - https://stackoverflow.com/questions/32300132/why-cant-i-store-a-value-and-a-reference-to-that-value-in-the-same-struct – Cerberus Jul 07 '21 at 08:15
  • As other users said, self-referential structs require `unsafe` code. I suggest you stick to the HashMap and forget about the vector. Take a look at [this playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=753e48596546d82eb214d8d33ee745a2) – Román Cárdenas Jul 07 '21 at 08:20

1 Answers1

2

The issue with this code is the following:

pub fn add(&mut self, user: User) {
    self.list.push(user);  // Here, you move user to the list. The list owns user
    self.index.insert(user.id, &user);  // Then, you create a reference of a moved value
}

So, in your UserIndex struct, you want to store values and references of these values. These are called self-referential structs. With the ownership rules of Rust, you need to use unsafe Rust code to achieve this. If I were you, I'd think of a different way of implementing your collection following the Rust conventions. For example, you could do something like this:

use std::collections::HashMap;

pub struct User {
    pub id: u32,
    pub name: String,
}

pub struct UsersIndex {
    index: HashMap<u32, User>,  // I only use the HashMap. The HashMap owns the User structs
}

impl UsersIndex {
    pub fn new() -> Self {
        UsersIndex {
            index: HashMap::new(),
        }
    }

    pub fn add(&mut self, user: User) {
        self.index.insert(user.id, user);  // user is moved to the HashMap
    }

    pub fn get(&self, id: u32) -> Option<&User> { // Instead of a nested reference, we get a regular reference
        self.index.get(&id)
    }
}

fn main() {
    let user = User {
        id: 42,
        name: "test".to_string(),
    };
    let mut index = UsersIndex::new();

    index.add(user);

    match index.get(42) {
        Some(usr) => println!("{}", usr.name),
        _ => println!("Not Found"),
    }
}

Here you can find a playground with this implementation.

EDIT

If you need different HashMaps for the same User structs depending on the key used, you can use Rc smart pointers. They allow you to create more than one pointer that owns the same struct. It would look more or less like this:

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

pub struct User {
    pub id: u32,
    pub name: String,
}

pub struct UsersIndex {
    index: HashMap<u32, Rc<User>>,  // Keys will be the user indeces
    name: HashMap<String, Rc<User>>, // Keys will be the user names
}

impl UsersIndex {
    pub fn new() -> Self {
        UsersIndex {
            index: HashMap::new(),
            name: HashMap::new()
        }
    }

    pub fn add(&mut self, user: User) {
        // user will be moved, so we copy the keys before that:
        let user_id = user.id;
        let user_name = user.name.clone();
        
        let user_rc = Rc::new(user);  // We create the Rc pointer; user is moved to user_rc
        self.index.insert(user_id, user_rc.clone());  // Rc pointers can be cloned
        self.name.insert(user_name, user_rc);  // user_rc is moved to the self.name HashMap
    }

    pub fn get(&self, id: u32) -> Option<Rc<User>> {
        match self.index.get(&id) {
            Some(user) => Some(user.clone()),
            None => None
        }
    }
}

fn main() {
    let user = User {
        id: 42,
        name: "test".to_string(),
    };
    let mut index = UsersIndex::new();

    index.add(user);

    match index.get(42) {
        Some(usr) => println!("{}", usr.name),
        _ => println!("Not Found"),
    }
}

Here you can find a playground with this new implementation. Again, if you also need the User structs to be mutable, then you'll probably need to use an Rc<RefCell<User>> (link here).

Hope you find this useful!

Román Cárdenas
  • 492
  • 5
  • 15
  • Perfect solution, until I need second hashmap for indexing by names – broadsw0rd Jul 07 '21 at 08:32
  • In that case, I recommend you use `Rc` smart pointers. Take a look at [this post](https://doc.rust-lang.org/book/ch15-04-rc.html) from the Rust documentation. With these, you can create multiple pointers that own the same struct. However, you won't be able to mutate this struct. If you want to mutate the struct, you can use an `Rc>` (take a look at [this link](https://doc.rust-lang.org/book/ch15-05-interior-mutability.html#having-multiple-owners-of-mutable-data-by-combining-rct-and-refcellt)). – Román Cárdenas Jul 07 '21 at 08:36
  • I added another use case that uses `Rc` smart pointers, so you can create more than one HashMap with the same `User` structs – Román Cárdenas Jul 07 '21 at 08:50
  • 1
    The Rc thing is only simple as long as you don't need to remove the elements. Then removing them from all the collections becomes fairly hard problem—you'll need a struct with the value and all the keys and use sets for the indices with suitable comparison adapters so you can get all the keys from the value. I'm sure somebody already implemented that as a crate. – Jan Hudec Jul 07 '21 at 09:01