How do you add indexes to a vec

I have a struct with there in a snapshot of a piece of dastabase. Now I want to make different indexes on it so that I can quickly find an object. How do I do that?

I have tried below but I can't figure it out with the lifetimes.

use std::collections::HashMap;

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

struct Home {
    id: u32,
    name: String,
    user_id: u32,
}

struct DbViewBuilder {
    pub users: Vec<User>,
    pub home: Vec<Home>,
}

impl DbViewBuilder {
    fn build<'a>(self) -> DbView<'a> {
        DbView {
            users: self.users,
            home: self.home,
            user_by_user_id: self
                .users
                .iter()
                .map(|s| (s.id, s))
                .collect::<HashMap<u32, &'a User>>(),
            home_by_home_id: HashMap::new(),
            homes_by_user_id: HashMap::new(),
        }
    }
}

struct DbView<'a> {
    users: Vec<User>,
    home: Vec<Home>,
    user_by_user_id: HashMap<u32, &'a User>,
    home_by_home_id: HashMap<u32, &'a Home>,
    homes_by_user_id: HashMap<u32, Vec<&'a Home>>,
}

impl<'a> DbView<'a> {
    // fn index(&mut self) {
    //     self.user_by_user_id = self
    //         .users
    //         .iter()
    //         .map(|s| (s.id, s))
    //         .collect::<HashMap<u32, &'a User>>();
    // }

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

    fn get_homes_by_user_id(&self, id: &u32) -> Option<&Vec<&Home>> {
        self.homes_by_user_id.get(id)
    }
}

#[cfg(test)]
mod tests {
    use crate::*;
 
    #[test]
    fn it_works() {
    
        let view = DbViewBuilder {
            user: vec![User {id: 32, name:"test_user".to_string()}],
            home: vec![],
        }.build();

    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0560]: struct `DbViewBuilder` has no field named `user`
  --> src/lib.rs:69:13
   |
69 |             user: vec![User {id: 32, name:"test_user".to_string()}],
   |             ^^^^ help: a field with a similar name exists: `users`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0560`.
error: could not compile `playground`

To learn more, run the command again with --verbose.
warning: build failed, waiting for other jobs to finish...
error[E0597]: `self.users` does not live long enough
  --> src/lib.rs:24:30
   |
20 |       fn build<'a>(self) -> DbView<'a> {
   |                -- lifetime `'a` defined here
...
24 |               user_by_user_id: self
   |  ______________________________^
25 | |                 .users
   | |______________________^ borrowed value does not live long enough
26 |                   .iter()
27 |                   .map(|s| (s.id, s))
   |                            --------- returning this value requires that `self.users` is borrowed for `'a`
...
32 |       }
   |       - `self.users` dropped here while still borrowed

error[E0382]: borrow of moved value: `self.users`
    --> src/lib.rs:24:30
     |
22   |               users: self.users,
     |                      ---------- value moved here
23   |               home: self.home,
24   |               user_by_user_id: self
     |  ______________________________^
25   | |                 .users
26   | |                 .iter()
     | |_______________________^ value borrowed here after move
     |
     = note: move occurs because `self.users` has type `Vec<User>`, which does not implement the `Copy` trait
     = note: borrow occurs due to deref coercion to `[User]`
note: deref defined here

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0382, E0597.
For more information about an error, try `rustc --explain E0382`.
error: build failed

You're trying to create a self-referential struct, which doesn't work with Rust's ownership model. The most direct way forward is to store a HashMap of indices instead, HashMap<u32, usize>.

Here's a modified version of your code on the playground, but I didn't do any testing, just outlined the idea and eliminated the compiler errors.

Alternatively you could keep the view and the data in completely different structs, so the view just references the data which lives in a separate struct (and doesn't simultaneously own the data).

1 Like

Thanks, i have update the playground and add the test and homes_by_user_id function.

playground

I will try also the other option.

I can't get this other option working. I have try this:

playground

use std::collections::HashMap;

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

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

#[derive(Debug)]
struct DbData {
    pub users: Vec<User>,
    pub homes: Vec<Home>,
}


#[derive(Debug)]
struct DbIndex<'a> {
    user_by_user_id: HashMap<u32, &'a User>,
    home_by_home_id: HashMap<u32, &'a Home>,
    homes_by_user_id: HashMap<u32, Vec<&'a Home>>,
}

#[derive(Debug)]
struct DbView<'a> {
    data: DbData,
    index: DbIndex<'a>,
}


impl DbData {
    fn build_index(&self) -> DbIndex {
        let user_by_user_id = self.users.iter().map(|u| (u.id, u)).collect();

        let homes_by_user_id =
            self.homes
                .iter()
                .fold(HashMap::new(), |mut acc, home| {
                    acc.entry(home.user_id).or_insert(Vec::new()).push(home);
                    acc
                });

        DbIndex {
            user_by_user_id,
            home_by_home_id: HashMap::new(),
            homes_by_user_id: homes_by_user_id,
        }
    }
}

impl DbIndex<'_> {
    fn get_user_by_id(&self, id: &u32) -> Option<&&User> {
        self.user_by_user_id.get(id)
    }

    fn get_homes_by_user_id(&self, id: &u32) -> Option<&Vec<&Home>> {
        self.homes_by_user_id.get(id)
    }
}

#[cfg(test)]
mod tests {
    use crate::*;

    #[test]
    fn it_works() -> Result<(), ()> {
        let data = DbData {
            users: vec![
                User {
                    id: 10,
                    name: "test_user 10".to_string(),
                },
                User {
                    id: 11,
                    name: "test_user 11".to_string(),
                },
            ],
            homes: vec![
                Home {
                    id: 21,
                    name: "Home A".to_string(),
                    user_id: 10,
                },
                Home {
                    id: 21,
                    name: "Home B".to_string(),
                    user_id: 10,
                },
                Home {
                    id: 21,
                    name: "Home C".to_string(),
                    user_id: 11,
                },
            ],
        };

        let index = data.build_index();
        
        
        let view = DbView {
            data,
            index,
        };

        dbg!(&view);

        assert_eq!(
            view.index.get_user_by_id(&10).ok_or(())?.name,
            "test_user 10".to_string()
        );

        assert_eq!(
            dbg!(view.index.get_homes_by_user_id(&10))
                .ok_or(())?
                .iter()
                .map(|h| h.name.clone())
                .collect::<Vec<String>>(),
            vec!["Home A".to_string(), "Home B".to_string()]
        );

        Ok(())
    }
}

But i get thinks like

   Compiling playground v0.0.1 (/playground)
warning: associated function is never used: `build_index`
  --> src/lib.rs:38:8
   |
38 |     fn build_index(&self) -> DbIndex {
   |        ^^^^^^^^^^^
   |
   = note: `#[warn(dead_code)]` on by default

warning: associated function is never used: `get_user_by_id`
  --> src/lib.rs:58:8
   |
58 |     fn get_user_by_id(&self, id: &u32) -> Option<&&User> {
   |        ^^^^^^^^^^^^^^

warning: associated function is never used: `get_homes_by_user_id`
  --> src/lib.rs:62:8
   |
62 |     fn get_homes_by_user_id(&self, id: &u32) -> Option<&Vec<&Home>> {
   |        ^^^^^^^^^^^^^^^^^^^^

error[E0505]: cannot move out of `data` because it is borrowed
   --> src/lib.rs:107:13
    |
103 |         let index = data.build_index();
    |                     ---- borrow of `data` occurs here
...
107 |             data,
    |             ^^^^ move out of `data` occurs here
108 |             index,
    |             ----- borrow later used here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0505`.
error: could not compile `playground`

To learn more, run the command again with --verbose.
warning: build failed, waiting for other jobs to finish...
warning: 3 warnings emitted

error: build failed

Your DbData and DbIndex are the separation I was refering to, and that works. Here's your code with just the DbView parts commented out and the tests adjusted accordingly: Playground.

(Side note, your last two playground links are to the same URL. You may have forgotten to generate a new link after editing your code the second time.)

When you combine DbData and DbIndex in a DbView, you're back to your original struct that contained both the data and references to it -- a self-referential struct -- the only difference is a layer of indirection.

If you still need the straight up lists of users and homes in your DbIndex, you can add that with slices as well.