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}