From 163ba1bae80add675052b623564688e55658077c Mon Sep 17 00:00:00 2001 From: DoumanAsh Date: Thu, 23 Nov 2023 23:30:36 +0900 Subject: [PATCH] Introduce Value wrapper --- .github/workflows/rust.yml | 44 +++++++----- src/lib.rs | 138 +++++++++++++------------------------ src/typ.rs | 13 ++++ src/value.rs | 129 ++++++++++++++++++++++++++++++++++ tests/type_map.rs | 41 +++-------- 5 files changed, 227 insertions(+), 138 deletions(-) create mode 100644 src/typ.rs create mode 100644 src/value.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f8d5be1..a75883f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,15 +1,27 @@ name: Rust -on: [push, pull_request] +on: + push: + branches: + - master + paths: + - '.github/workflows/rust.yml' + - 'src/**.rs' + - 'tests/**' + - 'Cargo.toml' + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: + - '**' + paths: + - '.github/workflows/rust.yml' + - 'src/**.rs' + - 'tests/**' + - 'Cargo.toml' jobs: build: - - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: [macos-latest, windows-latest, ubuntu-latest] + runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 @@ -23,16 +35,11 @@ jobs: curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal --default-toolchain stable echo ::add-path::$HOME/.cargo/bin fi - - name: Install Rust Windows - if: runner.os == 'Windows' + + - name: Install Miri run: | - if (Get-Command "rustup" -ErrorAction SilentlyContinue) { - rustup update - } else { - Invoke-WebRequest https://static.rust-lang.org/rustup/dist/i686-pc-windows-gnu/rustup-init.exe -OutFile rustup-init.exe - ./rustup-init.exe -y --profile minimal --default-toolchain stable - echo ::add-path::%USERPROFILE%\.cargo\bin - } + rustup toolchain install nightly + rustup +nightly component add miri - name: Rust version run: | @@ -41,3 +48,8 @@ jobs: - name: Test run: cargo test + + - name: Miri Test + run: | + cargo +nightly miri test + cargo +nightly miri test --release diff --git a/src/lib.rs b/src/lib.rs index 14dfa52..0959be0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,10 @@ //! Trivial type-map implementation //! //! Implementation uses type erased values with type as index. -//! Due to limitation of `TypeId` only types without non-static references are supported. (in future it can be changed) //! //! ## Hash implementation //! -//! The map uses simplified `Hasher` that relies on fact that `TypeId` produces unique values only. +//! The map uses simplified `Hasher` that relies on fact that `Type::id` is unique. //! In fact there is no hashing under hood, and type's id is returned as it is. //! //! ## Usage @@ -27,18 +26,7 @@ //! ``` #![warn(missing_docs)] - -use core::any::TypeId; - -mod hash; - -type Key = TypeId; - -#[cold] -#[inline(never)] -fn unlikely_vacant_insert(this: std::collections::hash_map::VacantEntry<'_, Key, ValueBox>, val: ValueBox) -> &'_ mut ValueBox { - this.insert(val) -} +#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))] #[cfg(not(debug_assertions))] macro_rules! unreach { @@ -56,6 +44,22 @@ macro_rules! unreach { }) } +mod typ; +pub use typ::Type; +mod value; +pub use value::Value; +mod hash; + +type Key = core::any::TypeId; +///Boxed [Type] +pub type ValueBox = Box; + +#[cold] +#[inline(never)] +fn unlikely_vacant_insert(this: std::collections::hash_map::VacantEntry<'_, Key, ValueBox>, val: ValueBox) -> &'_ mut ValueBox { + this.insert(val) +} + type HashMap = std::collections::HashMap; ///Type-safe store, indexed by types. @@ -63,17 +67,6 @@ pub struct TypeMap { inner: HashMap, } -///Valid type for [TypeMap] -pub trait Type: 'static + Send + Sync {} -impl Type for T {} - -///Shared reference to [Type] -pub type ValueRef<'a> = &'a (dyn core::any::Any + Send + Sync); -///Mutable reference to [Type] -pub type ValueMut<'a> = &'a mut (dyn core::any::Any + Send + Sync); -///Boxed [Type] -pub type ValueBox = Box; - impl TypeMap { #[inline] ///Creates new instance @@ -110,55 +103,37 @@ impl TypeMap { #[inline] ///Returns whether element is present in the map. pub fn has(&self) -> bool { - self.inner.contains_key(&TypeId::of::()) + self.inner.contains_key(&T::id()) } #[inline] ///Returns whether element is present in the map. pub fn contains_key(&self) -> bool { - self.inner.contains_key(&TypeId::of::()) + self.inner.contains_key(&T::id()) } #[inline] ///Access element in the map, returning reference to it, if present pub fn get(&self) -> Option<&T> { - match self.inner.get(&TypeId::of::()) { - Some(ptr) => match ptr.downcast_ref() { - Some(res) => Some(res), - None => unreach!(), - }, - None => None - } + self.get_raw::().map(Value::downcast_ref) } #[inline] - ///Access element in the map with type-id provided at runtime, returning reference to it, if present - pub fn get_raw(&self, k: TypeId) -> Option { - match self.inner.get(&k) { - Some(ptr) => Some(ptr.as_ref()), - None => None - } + ///Access element in the map, returning reference to it, if present + pub fn get_raw(&self) -> Option<&Value> { + self.inner.get(&T::id()).map(Value::new_inner_ref) } #[inline] ///Access element in the map, returning mutable reference to it, if present pub fn get_mut(&mut self) -> Option<&mut T> { - match self.inner.get_mut(&TypeId::of::()) { - Some(ptr) => match ptr.downcast_mut() { - Some(res) => Some(res), - None => unreach!(), - }, - None => None - } + self.get_mut_raw::().map(Value::downcast_mut) } #[inline] - ///Access element in the map with type-id provided at runtime, returning mutable reference to it, if present - pub fn get_mut_raw(&mut self, k: TypeId) -> Option { - match self.inner.get_mut(&k) { - Some(ptr) => Some(ptr.as_mut()), - None => None - } + ///Access element in the map, returning mutable reference to it, if present + pub fn get_mut_raw(&mut self) -> Option<&mut Value> { + self.inner.get_mut(&T::id()).map(Value::new_inner_mut) } #[inline] @@ -166,7 +141,7 @@ impl TypeMap { pub fn get_or_default(&mut self) -> &mut T { use std::collections::hash_map::Entry; - match self.inner.entry(TypeId::of::()) { + match self.inner.entry(T::id()) { Entry::Occupied(occupied) => { match occupied.into_mut().downcast_mut() { Some(res) => res, @@ -174,7 +149,7 @@ impl TypeMap { } }, Entry::Vacant(vacant) => { - let ptr = unlikely_vacant_insert(vacant, Box::new(T::default())); + let ptr = unlikely_vacant_insert(vacant, Box::::default()); match ptr.downcast_mut() { Some(res) => res, None => unreach!(), @@ -183,6 +158,7 @@ impl TypeMap { } } + #[inline] ///Insert element inside the map, returning heap-allocated old one if any /// ///## Note @@ -191,56 +167,36 @@ impl TypeMap { ///Some special types like function pointers are impossible to infer as non-anonymous type. ///You should manually specify type when in doubt. pub fn insert(&mut self, value: T) -> Option> { - use std::collections::hash_map::Entry; - - match self.inner.entry(TypeId::of::()) { - Entry::Occupied(mut occupied) => { - let result = occupied.insert(Box::new(value)); - match result.downcast() { - Ok(result) => Some(result), - Err(_) => unreach!() - } - }, - Entry::Vacant(vacant) => { - vacant.insert(Box::new(value)); - None - } - } + self.insert_raw(Value::new_inner(Box::new(value))).map(Value::downcast) } - ///Insert boxed element inside the map with dynamic type, - ///returning heap-allocated old one with the same type-id if any. - /// - ///This does not reallocate `value`. - pub fn insert_raw(&mut self, value: ValueBox) -> Option { + ///Insert raw element inside the map, returning heap-allocated old one if any + pub fn insert_raw(&mut self, value: Value) -> Option> { use std::collections::hash_map::Entry; - match self.inner.entry(value.as_ref().type_id()) { - Entry::Occupied(mut occupied) => { - let result = occupied.insert(value); - Some(result) - }, + match self.inner.entry(T::id()) { + Entry::Occupied(mut occupied) => Some( + Value::::new_inner( + occupied.insert(value.into_raw()) + ) + ), Entry::Vacant(vacant) => { - vacant.insert(value); + vacant.insert(value.into_raw()); None } } } + #[inline] ///Attempts to remove element from the map, returning boxed `Some` if it is present. - pub fn remove(&mut self) -> Option> { - self.inner.remove(&TypeId::of::()).map(|ptr| { - match ptr.downcast() { - Ok(result) => result, - Err(_) => unreach!() - } - }) + pub fn remove_raw(&mut self) -> Option> { + self.inner.remove(&T::id()).map(Value::new_inner) } #[inline] - ///Attempts to remove element from the map with type-id provided at runtime, returning boxed `Some` if it is present. - pub fn remove_raw(&mut self, id: TypeId) -> Option { - self.inner.remove(&id) + ///Attempts to remove element from the map, returning boxed `Some` if it is present. + pub fn remove(&mut self) -> Option> { + self.inner.remove(&T::id()).map(|val| Value::::new_inner(val).downcast()) } } diff --git a/src/typ.rs b/src/typ.rs new file mode 100644 index 0000000..5d61fa7 --- /dev/null +++ b/src/typ.rs @@ -0,0 +1,13 @@ +use core::any::TypeId; + +///Valid type for [TypeMap] +pub trait Type: 'static + Send + Sync { + #[doc(hidden)] + #[inline(always)] + ///Return type id + fn id() -> TypeId { + TypeId::of::() + } +} + +impl Type for T {} diff --git a/src/value.rs b/src/value.rs new file mode 100644 index 0000000..ae1925b --- /dev/null +++ b/src/value.rs @@ -0,0 +1,129 @@ +use crate::{Type, ValueBox}; + +use core::marker::PhantomData; + +#[repr(transparent)] +///Value type +pub struct Value { + inner: ValueBox, + _typ: PhantomData +} + +impl Value { + #[inline(always)] + ///Creates new raw Value trusting user to specify correct type + pub unsafe fn new(inner: ValueBox) -> Self { + Self::new_inner(inner) + } + + #[inline(always)] + pub(crate) fn new_inner(inner: ValueBox) -> Self { + Self { + inner, + _typ: PhantomData, + } + } + + #[inline(always)] + pub(crate) fn new_inner_ref(inner: &ValueBox) -> &Self { + unsafe { + core::mem::transmute(inner) + } + } + + #[inline(always)] + pub(crate) fn new_inner_mut(inner: &mut ValueBox) -> &mut Self { + unsafe { + core::mem::transmute(inner) + } + } + + #[inline(always)] + ///Creates instance from concrete type + pub fn from_boxed(inner: Box) -> Self { + Self::new_inner(inner) + } + + #[inline] + ///Downcasts self into concrete type + pub fn downcast(self) -> Box { + match self.inner.downcast() { + Ok(res) => res, + Err(_) => unreach!(), + } + } + + #[inline] + ///Downcasts self into concrete type + pub fn downcast_ref(&self) -> &T { + match self.inner.downcast_ref() { + Some(res) => res, + None => unreach!(), + } + } + + #[inline] + ///Downcasts self into concrete type + pub fn downcast_mut(&mut self) -> &mut T { + match self.inner.downcast_mut() { + Some(res) => res, + None => unreach!(), + } + } + + #[inline(always)] + ///Access underlying untyped pointer + pub fn as_raw(&self) -> &ValueBox { + &self.inner + } + + #[inline(always)] + ///Access underlying untyped pointer + pub fn as_raw_mut(&mut self) -> &mut ValueBox { + &mut self.inner + } + + + #[inline(always)] + ///Access underlying untyped pointer + pub fn into_raw(self) -> ValueBox { + self.inner + } +} + +impl AsRef for Value { + #[inline(always)] + fn as_ref(&self) -> &T { + self.downcast_ref() + } +} + +impl AsMut for Value { + #[inline(always)] + fn as_mut(&mut self) -> &mut T { + self.downcast_mut() + } +} + +impl core::ops::Deref for Value { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.downcast_ref() + } +} + +impl core::ops::DerefMut for Value { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + self.downcast_mut() + } +} + +impl Into for Value { + #[inline(always)] + fn into(self) -> ValueBox { + self.into_raw() + } +} diff --git a/tests/type_map.rs b/tests/type_map.rs index 6cf1c4f..ff0b342 100644 --- a/tests/type_map.rs +++ b/tests/type_map.rs @@ -1,5 +1,13 @@ -use std::any::TypeId; -use ttmap::{TypeMap, ValueBox}; +use ttmap::TypeMap; + +use core::mem; + +#[test] +fn check_type_sizes() { + assert_eq!(mem::size_of::>>(), mem::size_of::() * 2); + assert_eq!(mem::size_of::<&'static dyn PartialEq>(), mem::size_of::() * 2); + assert_eq!(mem::size_of::>(), mem::size_of::() * 2); +} #[test] fn check_type_map() { @@ -43,35 +51,6 @@ fn check_type_map() { assert!(map.is_empty()); } -#[test] -fn check_raw() { - let mut map = TypeMap::new(); - - assert!(map.is_empty()); - assert_eq!(map.len(), 0); - - assert!(map.insert("test").is_none()); - assert_eq!(*(*map.insert_raw(Box::new("lolka") as ValueBox).unwrap()).downcast_ref::<&'static str>().unwrap(), "test"); - assert_eq!(*map.get::<&'static str>().unwrap(), "lolka"); - assert_eq!(*map.get_raw(TypeId::of::<&'static str>()).unwrap().downcast_ref::<&'static str>().unwrap(), "lolka"); - assert!(map.get::().is_none()); - assert!(map.get_raw(TypeId::of::()).is_none()); - - *map.get_mut_raw(TypeId::of::<&'static str>()).unwrap().downcast_mut::<&'static str>().unwrap() = "abc"; - assert_eq!(*map.get::<&'static str>().unwrap(), "abc"); - assert_eq!(*map.get_raw(TypeId::of::<&'static str>()).unwrap().downcast_ref::<&'static str>().unwrap(), "abc"); - assert!(map.get::().is_none()); - assert!(map.get_raw(TypeId::of::()).is_none()); - - let str_box = map.remove_raw(TypeId::of::<&'static str>()).unwrap(); - assert!(map.remove_raw(TypeId::of::<&'static str>()).is_none()); - assert!(map.get::<&'static str>().is_none()); - assert!(map.get_raw(TypeId::of::<&'static str>()).is_none()); - assert_eq!(str_box.as_ref().type_id(), TypeId::of::<&'static str>()); - let str_box = str_box.downcast::().unwrap_err(); - assert_eq!(*str_box.downcast::<&'static str>().unwrap(), "abc"); -} - #[test] fn check_dtor_called() { let mut is_called = false;