1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
//! This crate provides special handling for reference primitive type.
//!
//! We know that the value a reference pointers to is kept alive during the full
//! lifetime of the reference.
//!
//! Many times a mutable reference is consumed by some specific handling,
//! but they didn't bother return the mutable reference back to the caller even
//! if they didn't give meaningful results. At most time this is fine, since you can
//! just recreate the reference by borrow again.
//! However there is a problem when the reference comes from an argument.
//!
//! This crate catches this pattern and provide it as a trait `TryTransform`,
//! making it possible without any NLL support.
//!
//! ```
//! # use std::hash::Hash;
//! # use std::collections::HashMap;
//! #
//! use try_transform_mut::TryTransform;
//! 
//! fn get_default<'r, K: Hash + Eq + Copy, V: Default>(
//!     map: &'r mut HashMap<K, V>,
//!     key: K,
//! ) -> &'r mut V {
//!     match map.try_transform(|m| m.get_mut(&key)) {
//!         Ok(value) => {
//!             value
//!         }
//!         Err(map) => {
//!             map.insert(key, V::default());
//!             map.get_mut(&key).unwrap()
//!         }
//!     }
//! }
//! ```
//!

/// A trait providing `try_transform` method to reference primitive type.
pub trait TryTransform {
    /// Try to consume a reference and transform it to `Some(B)` with a closure.
    /// If failed and returned `None`, return the original reference value.
    ///
    /// Especially useful for mutable references.
    fn try_transform<B, F>(self, f: F) -> Result<B, Self>
    where
        Self: Sized,
        F: FnOnce(Self) -> Option<B>;
}

impl<'a, T> TryTransform for &'a T {
    fn try_transform<B, F>(self, f: F) -> Result<B, Self>
    where
        Self: Sized,
        F: FnOnce(Self) -> Option<B>,
    {
        if let Some(v) = f(self) {
            return Ok(v);
        }

        Err(self)
    }
}

impl<'a, T> TryTransform for &'a mut T {
    fn try_transform<B, F>(self, f: F) -> Result<B, Self>
    where
        Self: Sized,
        F: FnOnce(Self) -> Option<B>,
    {
        let this: *mut T = self as _;

        if let Some(v) = f(self) {
            return Ok(v);
        }

        Err(unsafe { this.as_mut().unwrap() })
    }
}

#[cfg(test)]
mod tests {
    use std::hash::Hash;
    use std::collections::HashMap;

    use super::TryTransform;

    fn get_default<'r, K: Hash + Eq + Copy, V: Default>(
        map: &'r mut HashMap<K, V>,
        key: K,
    ) -> &'r mut V {
        match map.try_transform(|m| m.get_mut(&key)) {
            Ok(value) => value,
            Err(map) => {
                map.insert(key, V::default());
                map.get_mut(&key).unwrap()
            }
        }
    }

    #[test]
    fn it_works() {
        let mut a: HashMap<usize, usize> = HashMap::new();
        get_default(&mut a, 2);
    }
}