tears 0.10.0

A simple and elegant framework for building TUI applications using The Elm Architecture (TEA)
Documentation
//! Shared structural-key erasure for command and subscription lifecycle IDs.
//!
//! This implements the private helper described in RFC 0005 section 8.1 while
//! letting each ID apply the namespace rules required by its lifecycle model.

use std::any::{Any, TypeId, type_name};
use std::hash::{Hash, Hasher};
use std::sync::Arc;

/// An owned, type-erased key with structural equality and hashing.
///
/// The default [`Eq`] and [`Hash`] implementations include the concrete key
/// type. Owners that already provide a type namespace may use the value-only
/// operations to avoid hashing the same type distinction twice.
#[derive(Clone)]
pub struct StructuralKey {
    inner: Arc<dyn ErasedStructuralKey>,
}

impl StructuralKey {
    pub fn new<T>(value: T) -> Self
    where
        T: Eq + Hash + Send + Sync + 'static,
    {
        Self {
            inner: Arc::new(TypedStructuralKey(value)),
        }
    }

    pub fn type_name(&self) -> &'static str {
        self.inner.type_name()
    }

    /// Compares only the erased structural value.
    ///
    /// The erased implementation still downcasts safely before comparing. This
    /// operation is intended for an owner whose own namespace guarantees the
    /// concrete key type, such as a subscription source type and its associated
    /// `Key`.
    pub fn value_eq(&self, other: &Self) -> bool {
        self.inner.eq_erased(other.inner.as_ref())
    }

    /// Hashes only the erased structural value.
    ///
    /// The caller must provide the namespace that fixes the concrete key type.
    pub fn hash_value<H: Hasher>(&self, state: &mut H) {
        self.inner.hash_erased(state);
    }

    fn type_id(&self) -> TypeId {
        self.inner.erased_type_id()
    }
}

impl PartialEq for StructuralKey {
    fn eq(&self, other: &Self) -> bool {
        self.type_id() == other.type_id() && self.value_eq(other)
    }
}

impl Eq for StructuralKey {}

impl Hash for StructuralKey {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.type_id().hash(state);
        self.hash_value(state);
    }
}

trait ErasedStructuralKey: Send + Sync {
    fn as_any(&self) -> &dyn Any;
    fn erased_type_id(&self) -> TypeId;
    fn type_name(&self) -> &'static str;
    fn eq_erased(&self, other: &dyn ErasedStructuralKey) -> bool;
    fn hash_erased(&self, state: &mut dyn Hasher);
}

struct TypedStructuralKey<T>(T);

impl<T> ErasedStructuralKey for TypedStructuralKey<T>
where
    T: Eq + Hash + Send + Sync + 'static,
{
    fn as_any(&self) -> &dyn Any {
        self
    }

    fn erased_type_id(&self) -> TypeId {
        TypeId::of::<T>()
    }

    fn type_name(&self) -> &'static str {
        type_name::<T>()
    }

    fn eq_erased(&self, other: &dyn ErasedStructuralKey) -> bool {
        other
            .as_any()
            .downcast_ref::<Self>()
            .is_some_and(|other| self.0 == other.0)
    }

    fn hash_erased(&self, mut state: &mut dyn Hasher) {
        self.0.hash(&mut state);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::hash_map::DefaultHasher;

    fn hash(key: &StructuralKey) -> u64 {
        let mut hasher = DefaultHasher::new();
        key.hash(&mut hasher);
        hasher.finish()
    }

    fn hash_value(key: &StructuralKey) -> u64 {
        let mut hasher = DefaultHasher::new();
        key.hash_value(&mut hasher);
        hasher.finish()
    }

    #[test]
    fn equality_is_structural_and_type_sensitive() {
        assert!(StructuralKey::new(7_u64) == StructuralKey::new(7_u64));
        assert!(StructuralKey::new(7_u64) != StructuralKey::new(8_u64));
        assert!(StructuralKey::new(7_u64) != StructuralKey::new(7_i64));
        assert!(!StructuralKey::new(7_u64).value_eq(&StructuralKey::new(7_i64)));
    }

    #[test]
    fn hash_collisions_do_not_make_distinct_values_equal() {
        #[derive(Eq, PartialEq)]
        struct Collision(u8);

        impl Hash for Collision {
            fn hash<H: Hasher>(&self, state: &mut H) {
                0_u8.hash(state);
            }
        }

        let first = StructuralKey::new(Collision(1));
        let second = StructuralKey::new(Collision(2));

        assert_eq!(hash(&first), hash(&second));
        assert!(first != second);
    }

    #[test]
    fn value_only_hash_does_not_add_the_key_type_namespace() {
        #[derive(Eq, PartialEq)]
        struct First;

        #[derive(Eq, PartialEq)]
        struct Second;

        impl Hash for First {
            fn hash<H: Hasher>(&self, state: &mut H) {
                0_u8.hash(state);
            }
        }

        impl Hash for Second {
            fn hash<H: Hasher>(&self, state: &mut H) {
                0_u8.hash(state);
            }
        }

        assert_eq!(
            hash_value(&StructuralKey::new(First)),
            hash_value(&StructuralKey::new(Second))
        );
    }
}