Last active
November 1, 2021 03:51
-
-
Save tesaguri/1ad2f5891e5b60775dbe22da44f4fac4 to your computer and use it in GitHub Desktop.
Poor man's type erasure in Rust
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::any::TypeId; | |
| use std::mem; | |
| #[derive(Debug)] | |
| pub struct Untyped { | |
| type_id: TypeId, | |
| data: *mut [()], | |
| } | |
| impl Untyped { | |
| pub fn new<T: 'static>(t: T) -> Self { | |
| Untyped::from_box(Box::new(t)) | |
| } | |
| pub fn from_box<T: 'static + ?Sized>(boxed: Box<T>) -> Self { | |
| unsafe { | |
| let ptr: *mut T = Box::into_raw(boxed); | |
| let data = if mem::size_of::<*mut T>() == mem::size_of::<*mut [()]>() { // T: !Sized | |
| *(&ptr as *const _ as *const *mut [()]) | |
| } else { // T: Sized | |
| let as_fat = (ptr as *mut (), mem::uninitialized()); | |
| mem::transmute::<(*mut (), usize), *mut [()]>(as_fat) | |
| }; | |
| Untyped { | |
| type_id: TypeId::of::<T>(), | |
| data: data, | |
| } | |
| } | |
| } | |
| pub fn downcast<T: 'static + ?Sized>(self) -> Result<Box<T>, Self> { | |
| if TypeId::of::<T>() == self.type_id { | |
| unsafe { | |
| Ok(self.downcast_unchecked()) | |
| } | |
| } else { | |
| Err(self) | |
| } | |
| } | |
| pub unsafe fn downcast_unchecked<T: 'static + ?Sized>(self) -> Box<T> { | |
| Box::from_raw(*(&self.data as *const _ as *const *mut T)) | |
| } | |
| pub fn downcast_ref<T: 'static + ?Sized>(&self) -> Option<&T> { | |
| if TypeId::of::<T>() == self.type_id { | |
| unsafe { | |
| Some(self.downcast_ref_unchecked()) | |
| } | |
| } else { | |
| None | |
| } | |
| } | |
| pub unsafe fn downcast_ref_unchecked<T: 'static + ?Sized>(&self) -> &T { | |
| *(&self.data as *const _ as *const &T) | |
| } | |
| pub fn downcast_mut<T: 'static + ?Sized>(&mut self) -> Option<&mut T> { | |
| if TypeId::of::<T>() == self.type_id { | |
| unsafe { | |
| Some(self.downcast_mut_unchecked()) | |
| } | |
| } else { | |
| None | |
| } | |
| } | |
| pub unsafe fn downcast_mut_unchecked<T: 'static + ?Sized>(&mut self) -> &mut T { | |
| *(&self.data as *const _ as *const &mut T) | |
| } | |
| } | |
| #[cfg(test)] | |
| mod tests { | |
| use super::*; | |
| #[test] | |
| fn pass() { | |
| let mut s = "test"; | |
| let mut untyped = Untyped::new(s); | |
| assert_eq!(Some(&s), untyped.downcast_ref()); | |
| assert_eq!(Some(&mut s), untyped.downcast_mut()); | |
| assert_eq!(Box::new(s), untyped.downcast().unwrap()); | |
| } | |
| #[test] | |
| fn fail() { | |
| let s = "string"; | |
| let mut untyped = Untyped::new(s); | |
| assert!(untyped.downcast_ref::<u8>().is_none()); | |
| assert!(untyped.downcast_mut::<String>().is_none()); | |
| assert!(untyped.downcast::<[u8]>().is_err()); | |
| } | |
| #[test] | |
| fn from_box() { | |
| let s: &str = "boxed"; | |
| let mut untyped = Untyped::from_box(s.to_owned().into_boxed_str()); | |
| assert_eq!(Some(s), untyped.downcast_ref()); | |
| assert_eq!(Some(&mut s.to_owned() as &mut str), untyped.downcast_mut()); | |
| assert!(untyped.downcast_ref::<[u8]>().is_none()); | |
| assert_eq!(s.to_owned().into_boxed_str(), untyped.downcast().unwrap()); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment