Last active
December 3, 2019 21:14
-
-
Save sagebind/871333e2f8a8fb6c4e7f8ac79aa96bda to your computer and use it in GitHub Desktop.
Downcast like Any, but without trait objects
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, mem}; | |
/// A trait for zero-cost type casting in generic contexts to allow limited | |
/// forms of specialization at runtime. | |
/// | |
/// Similar to [`std::any::Any`], but does not require trait objects nor heap | |
/// allocation. Because of this, in most situations transmogrification will be | |
/// completely optimized away by the compiler, giving you effectively the same | |
/// performance as actual specialization. | |
pub trait Transmogrify: 'static { | |
/// Get a reference to self if it is of type `T`, or `None` if it isn't. | |
fn transmogrify_ref<T>(&self) -> Option<&T> | |
where T: Transmogrify, | |
{ | |
if TypeId::of::<Self>() == TypeId::of::<T>() { | |
Some(unsafe { | |
self.transmogrify_ref_unchecked() | |
}) | |
} else { | |
None | |
} | |
} | |
/// Get a mutable reference to self if it is of type `T`, or `None` if it | |
/// isn't. | |
fn transmogrify_mut<T>(&mut self) -> Option<&mut T> | |
where T: Transmogrify, | |
{ | |
if TypeId::of::<Self>() == TypeId::of::<T>() { | |
Some(unsafe { | |
self.transmogrify_mut_unchecked() | |
}) | |
} else { | |
None | |
} | |
} | |
/// Convert self into a `T` if self is of type `T`, consuming self in the | |
/// process. | |
/// | |
/// If self is not a `T`, returns self unchanged in an `Err`. | |
fn transmogrify_into<T>(self) -> Result<T, Self> | |
where | |
Self: Sized, | |
T: Transmogrify + Sized, | |
{ | |
if TypeId::of::<Self>() == TypeId::of::<T>() { | |
Ok(unsafe { | |
self.transmogrify_into_unchecked() | |
}) | |
} else { | |
Err(self) | |
} | |
} | |
/// Cast a reference of self to type `T`. | |
/// | |
/// This is unsafe because self might not be a `T`. | |
#[inline] | |
unsafe fn transmogrify_ref_unchecked<T>(&self) -> &T | |
where T: Transmogrify, | |
{ | |
&*(self as *const Self as *const _) | |
} | |
/// Cast a mutable reference of self to type `T`. | |
/// | |
/// This is unsafe because self might not be a `T`. | |
#[inline] | |
unsafe fn transmogrify_mut_unchecked<T>(&mut self) -> &mut T | |
where T: Transmogrify, | |
{ | |
&mut *(self as *mut Self as *mut _) | |
} | |
/// Cast self to type `T`, consuming self and moving it. | |
/// | |
/// This is unsafe because self might not be a `T`. | |
#[inline] | |
unsafe fn transmogrify_into_unchecked<T>(self) -> T | |
where | |
Self: Sized, | |
T: Transmogrify + Sized, | |
{ | |
let out = mem::transmute_copy(&self); | |
mem::forget(self); | |
out | |
} | |
} | |
/// Any static type can be transmogrified. | |
impl<T: 'static> Transmogrify for T {} | |
/// Example that demonstrates transmogrification. | |
fn print_dynamic<T: std::fmt::Debug + 'static>(value: T) { | |
// If value is a string, just print it. | |
if let Some(string) = value.transmogrify_ref::<String>() { | |
println!("string: {}", string); | |
} | |
// Use debug implementation for all other types. | |
else { | |
println!("debug: {:?}", value); | |
} | |
} | |
fn main() { | |
print_dynamic(23); | |
print_dynamic("hello"); | |
print_dynamic(String::from("hello")); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment