How to lazily get a default HashMap value that doesn't implement clone?

I want to get a value from a HashMap, or default to the last value in the HashMap, but it's values returns a reference which I cannot clone:

  let db_def: Option<&Mutex<Option<rusqlite::Connection>>> = by_name
    .get(&db_name)
    .or(by_name.values().collect::<Vec<&Mutex<Option<rusqlite::Connection>>>>().last());
     |
189  |     .or(by_name.values().collect::<Vec<&Mutex<Option<rusqlite::Connection>>>>().last());
     |      -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `std::sync::Mutex`, found reference
     |      |
     |      arguments to this function are incorrect
     |
     = note: expected enum `std::option::Option<&std::sync::Mutex<std::option::Option<Connection>>>`
                found enum `std::option::Option<&&std::sync::Mutex<std::option::Option<Connection>>>`

Is there some way to use or such that I can pass it a reference? into_values isn't an option since I cannot transfer ownership of this state.

The work around is that I pre-define the default, but then I'm unnecessarily processing a large Vec. I'd like to only get a default lazily, when the requested key isn't found. Is that possible?

let available = by_name.values().collect::<Vec<&Mutex<Option<rusqlite::Connection>>>>();
let db = if let Some(existing) = by_name.get(&db_name) {
    existing
} else {
    available.last().ok_or("no dbs exist")?
};

I see a few issues with the code:

  • HashMaps don't iterate in order, so there is no "last value in the HashMap"; it doesn't matter which position from the iterator you use
  • There's no reason to collect the results of an iterator if you just need one item
  • You should use .or_else so that you don't compute the fall-back value unconditionally as an argument to or

Taking all those into account, I get:

    let db_def: Option<&Mutex<Option<rusqlite::Connection>>> = by_name
        .get(&db_name)
        .or_else(|| by_name.values().next());

Playground.

3 Likes
.or(by_name.values().collect::<Vec<&Mutex<Option<rusqlite::Connection>>>>().last());

This code snippet will also always process the hashmap, since that expression is evaluated when passed to the function. (however, that operation is not that expensive since rust has pretty good optimizations for iterators).

I feel like the best solution to your issue would be making a new type which holds this logic by buffering the last element: link to playground

1 Like