try-insert-ext 0.1.0

Extends various types with `try_insert_with` methods.
Documentation
#[cfg(feature = "std")]
use std::collections::{btree_map, hash_map};

/// Extends map entries with `or_try_insert_with` and `or_try_insert_with_key`.
pub trait EntryInsertExt<'a, K, V> {
    /// If empty, computes the value from the default function. If the function
    /// returns `Ok`, inserts the value. If `f` returns `Err`, returns the
    /// error. If there is no error, returns a mutable reference to the value in
    /// the entry.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::collections::HashMap;
    ///
    /// use try_insert_ext::EntryInsertExt;
    ///
    /// let mut map: HashMap<&str, String> = HashMap::new();
    /// let s = "hoho".to_string();
    ///
    /// let e: Result<&mut String, ()> = map
    ///     .entry("poneyland")
    ///     .or_try_insert_with(|| Err(()));
    /// assert!(e.is_err());
    /// map.entry("poneyland").or_try_insert_with::<_, ()>(|| Ok(s));
    ///
    /// assert_eq!(map["poneyland"], "hoho".to_string());
    /// ```
    fn or_try_insert_with<F: FnOnce() -> Result<V, E>, E>(self, default: F)
        -> Result<&'a mut V, E>;

    /// If empty, computes the value from the default function. If the function
    /// returns `Ok`, inserts the value. If `f` returns `Err`, returns the
    /// error. If there is no error, returns a mutable reference to the value in
    /// the entry. This method allows for generating key-derived values for
    /// insertion by providing the default function a reference to the key
    /// that was moved during the `.entry(key)` method call.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::collections::HashMap;
    ///
    /// use try_insert_ext::EntryInsertExt;
    ///
    /// let mut map: HashMap<&str, usize> = HashMap::new();
    ///
    /// let e: Result<&mut usize, ()> = map
    ///     .entry("poneyland")
    ///     .or_try_insert_with_key(|_| Err(()));
    /// assert!(e.is_err());
    /// map
    ///     .entry("poneyland")
    ///     .or_try_insert_with_key::<_, ()>(|key| Ok(key.chars().count()));
    ///
    /// assert_eq!(map["poneyland"], 9);
    /// ```
    fn or_try_insert_with_key<F: FnOnce(&K) -> Result<V, E>, E>(
        self,
        default: F,
    ) -> Result<&'a mut V, E>;
}

#[cfg(feature = "std")]
impl<'a, K, V> EntryInsertExt<'a, K, V> for btree_map::Entry<'a, K, V>
where
    K: Ord,
{
    #[inline]
    fn or_try_insert_with<F: FnOnce() -> Result<V, E>, E>(
        self,
        default: F,
    ) -> Result<&'a mut V, E> {
        match self {
            Self::Occupied(entry) => Ok(entry.into_mut()),
            Self::Vacant(entry) => default().map(|default| entry.insert(default)),
        }
    }

    #[inline]
    fn or_try_insert_with_key<F: FnOnce(&K) -> Result<V, E>, E>(
        self,
        default: F,
    ) -> Result<&'a mut V, E> {
        match self {
            Self::Occupied(entry) => Ok(entry.into_mut()),
            Self::Vacant(entry) => default(entry.key()).map(|default| entry.insert(default)),
        }
    }
}

#[cfg(feature = "std")]
impl<'a, K, V> EntryInsertExt<'a, K, V> for hash_map::Entry<'a, K, V> {
    #[inline]
    fn or_try_insert_with<F: FnOnce() -> Result<V, E>, E>(
        self,
        default: F,
    ) -> Result<&'a mut V, E> {
        match self {
            Self::Occupied(entry) => Ok(entry.into_mut()),
            Self::Vacant(entry) => default().map(|default| entry.insert(default)),
        }
    }

    #[inline]
    fn or_try_insert_with_key<F: FnOnce(&K) -> Result<V, E>, E>(
        self,
        default: F,
    ) -> Result<&'a mut V, E> {
        match self {
            Self::Occupied(entry) => Ok(entry.into_mut()),
            Self::Vacant(entry) => default(entry.key()).map(|default| entry.insert(default)),
        }
    }
}

#[cfg(test)]
mod tests {
    #[cfg(feature = "std")]
    mod btree_map {
        use std::collections::BTreeMap;

        use crate::EntryInsertExt;

        #[test]
        fn it_works_when_occupied() {
            let mut map = BTreeMap::new();
            map.insert(0, 0);
            *map.entry(0).or_try_insert_with::<_, ()>(|| Ok(3)).unwrap() += 1;
            *map.entry(0).or_try_insert_with(|| Err(())).unwrap() += 1;
            assert_eq!(map.get(&0), Some(&2));
        }

        #[test]
        fn it_works_when_vacant() {
            let mut map = BTreeMap::new();
            map.entry(0).or_try_insert_with::<_, ()>(|| Ok(1)).unwrap();
            assert_eq!(map.get(&0), Some(&1));
        }

        #[test]
        fn it_errors() {
            let mut map = BTreeMap::<i32, i32>::new();
            assert_eq!(map.entry(0).or_try_insert_with(|| Err(())), Err(()));
        }
    }

    #[cfg(feature = "std")]
    mod hash_map {
        use std::collections::HashMap;

        use crate::EntryInsertExt;

        #[test]
        fn it_works_when_occupied() {
            let mut map = HashMap::new();
            map.insert(0, 0);
            *map.entry(0).or_try_insert_with::<_, ()>(|| Ok(3)).unwrap() += 1;
            *map.entry(0).or_try_insert_with(|| Err(())).unwrap() += 1;
            assert_eq!(map.get(&0), Some(&2));
        }

        #[test]
        fn it_works_when_vacant() {
            let mut map = HashMap::new();
            map.entry(0).or_try_insert_with::<_, ()>(|| Ok(1)).unwrap();
            assert_eq!(map.get(&0), Some(&1));
        }

        #[test]
        fn it_errors() {
            let mut map = HashMap::<i32, i32>::new();
            assert_eq!(map.entry(0).or_try_insert_with(|| Err(())), Err(()));
        }
    }
}