1
votes

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.

1
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
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 playgroundRomán Cárdenas

1 Answers

2
votes

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!