Yuchen
Yuchen

Reputation: 33036

Define static const hashmap of objects in Rust

We have a static list of objects. We would like to create a static constant HashMap with the key being the names of the objects.

How do we do that?

As an example, here is the definition of a Book object:

struct Book<'a> {
    name: &'a str,
    author: &'a str,
    id: i32,
}

Here is a static list of 3 books:

const ALL_BOOKS: &'static [Book] = &[
    Book {
        name: "Zero to One",
        author: "James",
        id: 2,
    },
    Book {
        name: "Zero to One",
        author: "Tom",
        id: 1,
    },
    Book {
        name: "Turtle all the way",
        author: "Jerry",
        id: 1,
    },
];

We want to defined a dictionary of books with the key being the name of the book, and the value being a list of book with that name.

I notice that we have lazy_static from:

Here is what I tried:

lazy_static! {
    static ref ALL_BOOKS_BY_SHORT_NAME: HashMap<String, Vec<Book<'static>>> = {
        let mut map = HashMap::new();

        for book in ALL_BOOKS {
            if !map.contains_key(book.name) {
                map.insert(book.name.to_owned(), vec![book]);
            } else {
                let mut list = map.get(book.name).unwrap();
                list.push(book)
            }
        }

        map
    };
}

Here is the error:

error[E0308]: mismatched types
  --> src/main.rs:42:9
   |
30 |     static ref ALL_BOOKS_BY_SHORT_NAME: HashMap<String, Vec<Book<'static>>> = {
   |                                         ----------------------------------- expected `HashMap<String, Vec<Book<'static>>>` because of return type
...
42 |         map
   |         ^^^ expected struct `Book`, found `&Book<'_>`
   |
   = note: expected struct `HashMap<_, Vec<Book<'static>>>`
              found struct `HashMap<_, Vec<&Book<'_>>>`

What does the '_ mean here? How do we solve that?

Upvotes: 0

Views: 3367

Answers (1)

Chayim Friedman
Chayim Friedman

Reputation: 70850

The '_ is "some lifetime". The actual lifetime is 'static, but the compiler doesn't know that at this stage of the compilation.

The problem is that you're iterating over a slice of Book<'static>s. And inserting items to the map. But iteration over a slice produces references, while your maps is expecting owned Books.

The fix is simple: clone or copy the books.

#[derive(Clone, Copy)]
struct Book<'a> { ... }

lazy_static! {
    static ref ALL_BOOKS_BY_SHORT_NAME: HashMap<String, Vec<Book<'static>>> = {
        let mut map = HashMap::new();

        for book in ALL_BOOKS {
            if !map.contains_key(book.name) {
                map.insert(book.name.to_owned(), vec![*book]);
            } else {
                let mut list = map.get(book.name).unwrap();
                list.push(*book)
            }
        }

        map
    };
}

Some more notes:

use once_cell::sync::Lazy;

static ALL_BOOKS_BY_SHORT_NAME: Lazy<HashMap<String, Vec<Book<'static>>>> = Lazy::new(|| {
    let mut map = HashMap::<_, Vec<_>>::new();

    for book in ALL_BOOKS {
        map.entry(book.name.to_owned()).or_default().push(*book);
    }

    map
});

This allocates a string even for duplicate keys but given that this is only an initialization routine this likely doesn't matter.

Upvotes: 3

Related Questions