extendable 0.1.0

A library for creating extendable types in Rust.
Documentation
#![forbid(unsafe_code)]
#![deny(missing_docs)]
//! A type that can be used to dynamically store data by type.
//!
//! # Examples
//! ```
//! use extendable::Extensions;
//!
//! let mut extensions = Extensions::default();
//! extensions.insert(false);
//!
//! let bool_ext = extensions.get::<bool>();
//! assert_eq!(bool_ext, Some(&false));
//! ```
use std::{
    any::{Any, TypeId},
    collections::HashMap,
    fmt::Debug,
};

/// A type that can be used to dynamically store data by type.
///
/// # Examples
///
/// ```
/// use extendable::Extensions;
///
/// let mut extensions = Extensions::default();
///
/// // Insert an extension with type `u32`.
/// extensions.insert::<u32>(42);
///
/// // Get a reference to the extension with type `u32` and asset its equal to 42.
/// assert_eq!(extensions.get::<u32>(), Some(&42));
/// ```
#[derive(Default)]
pub struct Extensions {
    map: HashMap<TypeId, Box<dyn Any>>,
}

impl Extensions {
    /// Gets a reference to the extension with type `T`.
    pub fn get<T: Any>(&self) -> Option<&T> {
        self.map
            .get(&TypeId::of::<T>())
            .and_then(|any| any.downcast_ref::<T>())
    }

    /// Gets a mutable reference to the extension with type `T`.
    pub fn get_mut<T: Any>(&mut self) -> Option<&mut T> {
        self.map
            .get_mut(&TypeId::of::<T>())
            .and_then(|any| any.downcast_mut::<T>())
    }

    /// Inserts an extension with type `T`.
    pub fn insert<T: Any>(&mut self, value: T) {
        self.map.insert(TypeId::of::<T>(), Box::new(value));
    }

    /// Removes an extension with type `T`.
    pub fn remove<T: Any>(&mut self) -> Option<T> {
        self.map
            .remove(&TypeId::of::<T>())
            .and_then(|any| any.downcast().ok().map(|boxed| *boxed))
    }

    /// Removes all extensions.
    pub fn clear(&mut self) {
        self.map.clear();
    }

    /// Returns `true` if the map contains an extension with type `T`.
    pub fn contains<T: Any>(&self) -> bool {
        self.map.contains_key(&TypeId::of::<T>())
    }

    /// Returns `true` if the map is empty.
    pub fn is_empty(&self) -> bool {
        self.map.is_empty()
    }

    /// Returns the number of extensions.
    pub fn len(&self) -> usize {
        self.map.len()
    }
}

impl Debug for Extensions {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // We don't want to print the actual values of the extensions since we don't keep their
        // Debug implementations.
        f.debug_struct("Extensions").finish()
    }
}

#[cfg(test)]
mod tests {
    use std::sync::{
        atomic::{AtomicUsize, Ordering},
        Arc,
    };

    use super::*;

    #[test]
    fn basic() {
        let mut extensions = Extensions::default();

        assert!(extensions.is_empty());

        extensions.insert::<u32>(42);

        assert_eq!(extensions.len(), 1);

        let value = extensions.get::<u32>().unwrap();
        assert_eq!(*value, 42);

        let value = extensions.get_mut::<u32>().unwrap();
        *value = 24;
        assert_eq!(*extensions.get::<u32>().unwrap(), 24);

        extensions.remove::<u32>().unwrap();

        assert!(extensions.is_empty());
    }

    #[test]
    fn drop_called() {
        #[derive(Debug)]
        struct DropCounter(Arc<AtomicUsize>);

        impl Drop for DropCounter {
            fn drop(&mut self) {
                self.0.fetch_add(1, Ordering::SeqCst);
            }
        }

        let drop_counter = Arc::new(AtomicUsize::new(0));
        {
            let mut extensions = Extensions::default();
            extensions.insert(DropCounter(drop_counter.clone()));
        }

        let count = drop_counter.load(Ordering::SeqCst);
        assert_eq!(count, 1);
    }
}