Skip to content

pre-RFC: Add a way to insert into a collection, then return a reference to the inserted item in one operation. #3343

@cyqsimon

Description

@cyqsimon

I ran across this issue today when I'm attempting to write a function like this:

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
struct Player {
    // fields omitted
}
struct PlayerList {
    // other fields omitted
    players: HashSet<Player>,
}
impl PlayerList {
    /// Create a new player and insert it into the player list.
    /// Then return a reference to this new player.
    pub fn new_player(
        &mut self,
        // other fields omitted
    ) -> &Player {
        let new = Player {};
        self.players.insert(new);
        todo!("return a reference to the new player")
    }
}

To my surprise, there is no easy way to do this with HashSet, or for that matter, with any collection. I ended up having to write this:

    pub fn new_player(&mut self) -> &Player {
        let new = Player {};
        self.players.insert(new.clone());
        self.players.get(&new).unwrap()
    }

This is obviously bad for a multitude of reasons:

  • Unnecessary Player::clone call
  • Unnecessary Hashset::get call
  • Extra unwrap
  • Generally bad ergonomics

So I asked on the Rust-lang discord guild here, and Yand.rs gave this solution:

use ::hashbrown::HashMap;

fn create_new(s: &mut HashMap<String, ()>) -> &str {
    let new = String::from("…");
    s.raw_entry_mut().from_key(&new).or_insert(new, ()).0
}

... but also had this to say:

Sadly it requires raw_entry_mut(), which is either unstable, or requires hashbrown. And also only manual maps have these, sets don't for some reason


My proposition

Add a family of associated functions that provide this capability to all collections:

impl<T> Vec<T> {
    pub fn insert_get(&mut self, index: usize, value: T) -> &T;
    pub fn push_get(&mut self, value: T) -> &T;
}
impl<T> VecDeque<T> {
    pub fn insert_get(&mut self, index: usize, value: T) -> &T;
    // might be unnecessary, see questions
    pub fn push_front_get(&mut self, value: T) -> &T;
    // might be unnecessary, see questions
    pub fn push_back_get(&mut self, value: T) -> &T;
}
impl<T> LinkedList<T> {
    // might be unnecessary, see questions
    pub fn push_front_get(&mut self, value: T) -> &T;
    // might be unnecessary, see questions
    pub fn push_back_get(&mut self, value: T) -> &T;
}
impl<K, V> HashMap<K, V> {
    pub fn insert_get(&mut self, key: K, value: V) -> (&V, Option<V>);
    // See https://github.com/rust-lang/rust/issues/82766#issuecomment-1301845589
    pub fn insert_vacant_get(&mut self, key: K, value: V) -> (&V, Option<V>);
}
impl<K, V> BTreeMap<K, V> {
    pub fn insert_get(&mut self, key: K, value: V) -> (&V, Option<V>);
    // See https://github.com/rust-lang/rust/issues/82766#issuecomment-1301845589
    pub fn insert_vacant_get(&mut self, key: K, value: V) -> (&V, Option<V>);
}
impl<T> HashSet<T> {
    pub fn replace_get(&mut self, value: T) -> (&T, Option<T>);
}
impl<T> BTreeSet<T> {
    pub fn replace_get(&mut self, value: T) -> (&T, Option<T>);
}
impl<T> BinaryHeap<T> {
    pub fn push_get(&mut self, value: T) -> &T;
}

Potential questions

  • The names are descriptive but not very elegant IMO. Are there better ones?
  • Should there be *_get_mut variants too?
  • VecDeque and LinkedList already have front and back methods. Do they make push_front_get and push_back_get redundant?
  • In the return type of maps and sets, should the order of tuple members be flipped? (i.e. (Option<V>, &V) instead)
  • For maps, should the return type contain a reference to the key as well? (i.e. (&K, &V, Option<V>) or ((&K, &V), Option<V>))

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions