Why does the entry `or_default` API consume the entry

I have a use case where I need to do the following things on a BTreeMap

  • Start with a reference to a key
  • Get or insert default and modify the value
  • Get a reference to the key that is in the HashMap (it's a unicase::Ascii, so potentially slightly different, I want to preserve the first case specified)

Maybe I'm missing something in the entry API but I can't seem to do this without constructing two owned keys.

type Map = BTreeMap<Ascii<String>, Vec<&'static str>>;

fn do_thing(map: &mut Map, key: &str, value: &'static str) -> String {
    let values = map.entry(Ascii::new(key.to_owned())).or_default();
    
    // check if we need to insert particular value
    let i = match values.iter().position(|v| *v == value) {
        Some(i) => i,
        None => {
            let i = values.len();
            values.push(value);
            i
        }
    };

    // now I need the key, in it's originally specified case
    let (orig_key, _) = map.get_key_value(&Ascii::new(key.to_owned())).unwrap();
    
    // Do something with original key and i
    // ...
    format!("{}-{}", orig_key, i)
}

BTreeMap::entry needs the key to be owned, because it will insert it into the map if no entry was encountered when VacantEntry::insert is called. There have been several attempts to enhance the entry API in this regard, but they have stalled long ago:

1 Like

The technical reason is that it returns a &'a mut V, where 'a is the lifetime of the map reference. There could be a variant that mutably borrows the entry but returns a reference with the lifetime of the borrowed entry, but it just isn't there.


Note however that the result of entry is a Entry enum that you can match on to get either a VacantEntry or an OccupiedEntry. Notably VacantEntry offers what you want: it gives you access to both the key and the inserted value. However unfortunately converting a VacantEntry to a OccupiedEntry by inserting a value is only available on the nightly compiler. You can probably work around this by computing i before inserting the value in case you're working with a VacantEntry, but that will probably require a bit of duplicated code.

1 Like