castaway/
utils.rs

1//! Low-level utility functions.
2
3use core::{
4    any::{type_name, TypeId},
5    marker::PhantomData,
6    mem, ptr,
7};
8
9/// Determine if two static, generic types are equal to each other.
10#[inline(always)]
11pub(crate) fn type_eq<T: 'static, U: 'static>() -> bool {
12    // Reduce the chance of `TypeId` collisions causing a problem by also
13    // verifying the layouts match and the type names match. Since `T` and `U`
14    // are known at compile time the compiler should optimize away these extra
15    // checks anyway.
16    mem::size_of::<T>() == mem::size_of::<U>()
17        && mem::align_of::<T>() == mem::align_of::<U>()
18        && mem::needs_drop::<T>() == mem::needs_drop::<U>()
19        && TypeId::of::<T>() == TypeId::of::<U>()
20        && type_name::<T>() == type_name::<U>()
21}
22
23/// Determine if two generic types which may not be static are equal to each
24/// other.
25///
26/// This function must be used with extreme discretion, as no lifetime checking
27/// is done. Meaning, this function considers `Struct<'a>` to be equal to
28/// `Struct<'b>`, even if either `'a` or `'b` outlives the other.
29#[inline(always)]
30pub(crate) fn type_eq_non_static<T: ?Sized, U: ?Sized>() -> bool {
31    non_static_type_id::<T>() == non_static_type_id::<U>()
32}
33
34/// Produces type IDs that are compatible with `TypeId::of::<T>`, but without
35/// `T: 'static` bound.
36fn non_static_type_id<T: ?Sized>() -> TypeId {
37    trait NonStaticAny {
38        fn get_type_id(&self) -> TypeId
39        where
40            Self: 'static;
41    }
42
43    impl<T: ?Sized> NonStaticAny for PhantomData<T> {
44        fn get_type_id(&self) -> TypeId
45        where
46            Self: 'static,
47        {
48            TypeId::of::<T>()
49        }
50    }
51
52    let phantom_data = PhantomData::<T>;
53    NonStaticAny::get_type_id(unsafe {
54        mem::transmute::<&dyn NonStaticAny, &(dyn NonStaticAny + 'static)>(&phantom_data)
55    })
56}
57
58/// Reinterprets the bits of a value of one type as another type.
59///
60/// Similar to [`std::mem::transmute`], except that it makes no compile-time
61/// guarantees about the layout of `T` or `U`, and is therefore even **more**
62/// dangerous than `transmute`. Extreme caution must be taken when using this
63/// function; it is up to the caller to assert that `T` and `U` have the same
64/// size and layout and that it is safe to do this conversion. Which it probably
65/// isn't, unless `T` and `U` are identical.
66///
67/// # Panics
68///
69/// This function panics if `T` and `U` have different sizes.
70///
71/// # Safety
72///
73/// It is up to the caller to uphold the following invariants:
74///
75/// - `T` must have the same alignment as `U`
76/// - `T` must be safe to transmute into `U`
77#[inline(always)]
78pub(crate) unsafe fn transmute_unchecked<T, U>(value: T) -> U {
79    // Assert is necessary to avoid miscompilation caused by a bug in LLVM.
80    // Without it `castaway::cast!(123_u8, (u8, u8))` returns `Ok(...)` on
81    // release build profile. `assert` shouldn't be replaced by `assert_eq`
82    // because with `assert_eq` Rust 1.70 and 1.71 will still miscompile it.
83    //
84    // See https://github.com/rust-lang/rust/issues/127286 for details.
85    assert!(
86        mem::size_of::<T>() == mem::size_of::<U>(),
87        "cannot transmute_unchecked if Dst and Src have different size"
88    );
89
90    let dest = ptr::read(&value as *const T as *const U);
91    mem::forget(value);
92    dest
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn non_static_type_comparisons() {
101        assert!(type_eq_non_static::<u8, u8>());
102        assert!(type_eq_non_static::<&'static u8, &'static u8>());
103        assert!(type_eq_non_static::<&u8, &'static u8>());
104
105        assert!(!type_eq_non_static::<u8, i8>());
106        assert!(!type_eq_non_static::<u8, &'static u8>());
107    }
108}