Skip to content

Commit

Permalink
Introduce Value wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
DoumanAsh committed Nov 23, 2023
1 parent 696e047 commit 163ba1b
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 138 deletions.
44 changes: 28 additions & 16 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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: |
Expand All @@ -41,3 +48,8 @@ jobs:
- name: Test
run: cargo test

- name: Miri Test
run: |
cargo +nightly miri test
cargo +nightly miri test --release
138 changes: 47 additions & 91 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 {
Expand All @@ -56,24 +44,29 @@ 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<dyn core::any::Any + Send + Sync>;

#[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<Key, ValueBox, hash::UniqueHasherBuilder>;

///Type-safe store, indexed by types.
pub struct TypeMap {
inner: HashMap,
}

///Valid type for [TypeMap]
pub trait Type: 'static + Send + Sync {}
impl<T: 'static + Send + Sync> 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<dyn core::any::Any + Send + Sync>;

impl TypeMap {
#[inline]
///Creates new instance
Expand Down Expand Up @@ -110,71 +103,53 @@ impl TypeMap {
#[inline]
///Returns whether element is present in the map.
pub fn has<T: Type>(&self) -> bool {
self.inner.contains_key(&TypeId::of::<T>())
self.inner.contains_key(&T::id())
}

#[inline]
///Returns whether element is present in the map.
pub fn contains_key<T: Type>(&self) -> bool {
self.inner.contains_key(&TypeId::of::<T>())
self.inner.contains_key(&T::id())
}

#[inline]
///Access element in the map, returning reference to it, if present
pub fn get<T: Type>(&self) -> Option<&T> {
match self.inner.get(&TypeId::of::<T>()) {
Some(ptr) => match ptr.downcast_ref() {
Some(res) => Some(res),
None => unreach!(),
},
None => None
}
self.get_raw::<T>().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<ValueRef> {
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<T: Type>(&self) -> Option<&Value<T>> {
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<T: Type>(&mut self) -> Option<&mut T> {
match self.inner.get_mut(&TypeId::of::<T>()) {
Some(ptr) => match ptr.downcast_mut() {
Some(res) => Some(res),
None => unreach!(),
},
None => None
}
self.get_mut_raw::<T>().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<ValueMut> {
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<T: Type>(&mut self) -> Option<&mut Value<T>> {
self.inner.get_mut(&T::id()).map(Value::new_inner_mut)
}

#[inline]
///Access element in the map, if not present, constructs it using default value.
pub fn get_or_default<T: Type + Default>(&mut self) -> &mut T {
use std::collections::hash_map::Entry;

match self.inner.entry(TypeId::of::<T>()) {
match self.inner.entry(T::id()) {
Entry::Occupied(occupied) => {
match occupied.into_mut().downcast_mut() {
Some(res) => res,
None => unreach!(),
}
},
Entry::Vacant(vacant) => {
let ptr = unlikely_vacant_insert(vacant, Box::new(T::default()));
let ptr = unlikely_vacant_insert(vacant, Box::<T>::default());
match ptr.downcast_mut() {
Some(res) => res,
None => unreach!(),
Expand All @@ -183,6 +158,7 @@ impl TypeMap {
}
}

#[inline]
///Insert element inside the map, returning heap-allocated old one if any
///
///## Note
Expand All @@ -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<T: Type>(&mut self, value: T) -> Option<Box<T>> {
use std::collections::hash_map::Entry;

match self.inner.entry(TypeId::of::<T>()) {
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<ValueBox> {
///Insert raw element inside the map, returning heap-allocated old one if any
pub fn insert_raw<T: Type>(&mut self, value: Value<T>) -> Option<Value<T>> {
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::<T>::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<T: Type>(&mut self) -> Option<Box<T>> {
self.inner.remove(&TypeId::of::<T>()).map(|ptr| {
match ptr.downcast() {
Ok(result) => result,
Err(_) => unreach!()
}
})
pub fn remove_raw<T: Type>(&mut self) -> Option<Value<T>> {
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<ValueBox> {
self.inner.remove(&id)
///Attempts to remove element from the map, returning boxed `Some` if it is present.
pub fn remove<T: Type>(&mut self) -> Option<Box<T>> {
self.inner.remove(&T::id()).map(|val| Value::<T>::new_inner(val).downcast())
}
}

Expand Down
13 changes: 13 additions & 0 deletions src/typ.rs
Original file line number Diff line number Diff line change
@@ -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::<Self>()
}
}

impl<T: 'static + Send + Sync> Type for T {}
Loading

0 comments on commit 163ba1b

Please sign in to comment.