Skip to content

Instantly share code, notes, and snippets.

@tesaguri
Last active November 1, 2021 03:51
Show Gist options
  • Select an option

  • Save tesaguri/1ad2f5891e5b60775dbe22da44f4fac4 to your computer and use it in GitHub Desktop.

Select an option

Save tesaguri/1ad2f5891e5b60775dbe22da44f4fac4 to your computer and use it in GitHub Desktop.
Poor man's type erasure in Rust
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