Skip to main content

next_gen_transmute/
lib.rs

1//! Stable backport of the unstable
2//! [Next-Gen Transmute](https://github.com/rust-lang/rust/issues/155079) functions
3//! [`transmute_prefix`] and [`transmute_neo`] to Rust 1.56
4
5#![no_std]
6
7use core::mem::{size_of, ManuallyDrop};
8
9/// Like [`transmute`](`core::mem::transmute`), but only initializes the "common prefix" of the
10/// first `min(size_of::<Src>(), size_of::<Dst>())` bytes of the destination from the
11/// corresponding bytes of the source.
12///
13/// This is equivalent to a "union cast" through a `union` with `#[repr(C)]`.
14///
15/// That means some size mismatches are not UB, like `[T; 2]` to `[T; 1]`.
16/// Increasing size is usually UB from being insufficiently initialized -- like
17/// `u8` to `u32` -- but isn't always.  For example, going from `u8` to
18/// `#[repr(C, align(4))] AlignedU8(u8);` is sound.
19///
20/// Prefer normal `transmute` where possible, for the extra checking, since
21/// both do exactly the same thing at runtime, if they both compile.
22///
23/// # Safety
24///
25/// If `size_of::<Src>() >= size_of::<Dst>()`, the first `size_of::<Dst>()` bytes
26/// of `src` must be be *valid* when interpreted as a `Dst`.  (In this case, the
27/// preconditions are the same as for `transmute_copy(&ManuallyDrop::new(src))`.)
28///
29/// If `size_of::<Src>() <= size_of::<Dst>()`, the bytes of `src` padded with
30/// uninitialized bytes afterwards up to a total size of `size_of::<Dst>()`
31/// must be *valid* when interpreted as a `Dst`.
32///
33/// In both cases, any safety preconditions of the `Dst` type must also be upheld.
34///
35/// # Examples
36///
37/// ```
38/// use next_gen_transmute::transmute_prefix;
39///
40/// assert_eq!(unsafe { transmute_prefix::<[i32; 4], [i32; 2]>([1, 2, 3, 4]) }, [1, 2]);
41///
42/// let expected = if cfg!(target_endian = "little") { 0x34 } else { 0x12 };
43/// assert_eq!(unsafe { transmute_prefix::<u16, u8>(0x1234) }, expected);
44///
45/// // Would be UB because the destination is incompletely initialized.
46/// // transmute_prefix::<u8, u16>(123)
47///
48/// // OK because the destination is allowed to be partially initialized.
49/// let _: std::mem::MaybeUninit<u16> = unsafe { transmute_prefix(123_u8) };
50/// ```
51pub const unsafe fn transmute_prefix<Src, Dst>(src: Src) -> Dst {
52    #[repr(C)]
53    union Transmute<A, B> {
54        a: ManuallyDrop<A>,
55        b: ManuallyDrop<B>,
56    }
57
58    // SAFETY: any safety invariants need to be upheld by the caller.
59    #[allow(unused_unsafe)]
60    unsafe {
61        ManuallyDrop::into_inner(
62            Transmute {
63                a: ManuallyDrop::new(src),
64            }
65            .b,
66        )
67    }
68}
69
70/// New version of `transmute`, exposed under this name so it can be iterated upon
71/// without risking breakage to uses of "real" transmute.
72///
73/// It will not be stabilized under this name.
74///
75/// # Examples
76///
77/// ```
78/// use next_gen_transmute::transmute_neo;
79///
80/// assert_eq!(unsafe { transmute_neo::<f32, u32>(0.0) }, 0);
81/// ```
82///
83/// ```compile_fail,E0080
84/// use next_gen_transmute::transmute_neo;
85///
86/// unsafe { transmute_neo::<u32, u16>(123) };
87/// ```
88pub const unsafe fn transmute_neo<Src, Dst>(src: Src) -> Dst {
89    struct AssertSameSize<A, B>(A, B);
90    impl<A, B> AssertSameSize<A, B> {
91        const ASSERT_SAME_SIZE: () = [()][!(size_of::<A>() == size_of::<B>()) as usize];
92    }
93    let () = AssertSameSize::<Src, Dst>::ASSERT_SAME_SIZE;
94
95    // SAFETY: the const-assert just checked that they're the same size,
96    // and any other safety invariants need to be upheld by the caller.
97    #[allow(unused_unsafe)]
98    unsafe {
99        transmute_prefix(src)
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use core::mem::MaybeUninit;
107
108    const U32_4: [u32; 4] = [0x23456789, 0x456789AB, 0x6789ABCD, 0x89ABCDEF];
109    const U32_2: [u32; 2] = [U32_4[0], U32_4[1]];
110    const U32: u32 = U32_4[0];
111    const I32_4: [i32; 4] = [U32_4[0] as _, U32_4[1] as _, U32_4[2] as _, U32_4[3] as _];
112    const U8_4_2: [[u8; 4]; 2] = [U32_2[0].to_ne_bytes(), U32_2[1].to_ne_bytes()];
113    const U8_8: [u8; 8] = [
114        U8_4_2[0][0],
115        U8_4_2[0][1],
116        U8_4_2[0][2],
117        U8_4_2[0][3],
118        U8_4_2[1][0],
119        U8_4_2[1][1],
120        U8_4_2[1][2],
121        U8_4_2[1][3],
122    ];
123
124    #[derive(Debug, PartialEq)]
125    #[repr(C, align(8))]
126    struct Align8U32(u32);
127
128    #[test]
129    fn bigger_to_smaller() {
130        assert!(U32_4.len() >= U32_2.len());
131        // SAFETY: every `[u32; M]` is a valid prefix of `[u32; N]` where `N >= M`.
132        let x: [u32; U32_2.len()] = unsafe { transmute_prefix(U32_4) };
133        assert_eq!(x, U32_2);
134    }
135
136    #[test]
137    fn smaller_to_bigger() {
138        // SAFETY: the destination does not have to be fully initialized.
139        let mut x: [MaybeUninit<u32>; U32_4.len()] = unsafe { transmute_prefix(U32_2) };
140        for i in U32_2.len()..U32_4.len() {
141            x[i].write(U32_4[i]);
142        }
143        // SAFETY: the source is now fully initialized.
144        let y: [u32; U32_4.len()] = unsafe { transmute_neo(x) };
145        assert_eq!(y, U32_4);
146    }
147
148    #[test]
149    fn array_to_first_element() {
150        assert!(U32_4.len() >= 1);
151        // SAFETY: every `u32` is a valid prefix of `[u32; N]` where `N >= 1`.
152        let x: u32 = unsafe { transmute_prefix(U32_4) };
153        assert_eq!(x, U32);
154    }
155
156    #[test]
157    fn first_element_to_array() {
158        // SAFETY: the destination does not have to be fully initialized.
159        let mut x: [MaybeUninit<u32>; U32_4.len()] = unsafe { transmute_prefix(U32) };
160        for i in 1..U32_4.len() {
161            x[i].write(U32_4[i]);
162        }
163        // SAFETY: the source is now fully initialized.
164        let y: [u32; U32_4.len()] = unsafe { transmute_neo(x) };
165        assert_eq!(y, U32_4);
166    }
167
168    #[test]
169    fn decrease_alignment() {
170        // SAFETY: every `u32` is a valid prefix of `Align8U32`.
171        let x: u32 = unsafe { transmute_prefix(Align8U32(U32)) };
172        assert_eq!(x, U32);
173    }
174
175    #[test]
176    fn increase_alignment() {
177        // SAFETY: every `u32` is a valid prefix of `Align8U32`,
178        // and the rest does not have to be initialized.
179        let x: Align8U32 = unsafe { transmute_prefix(U32) };
180        assert_eq!(x, Align8U32(U32));
181    }
182
183    #[test]
184    fn cast_signed() {
185        // SAFETY: every `[u32; 4]` is a valid `[i32; 4]`.
186        let x: [i32; U32_4.len()] = unsafe { transmute_neo(U32_4) };
187        assert_eq!(x, I32_4);
188    }
189
190    #[test]
191    fn cast_unsigned() {
192        // SAFETY: every `[i32; 4]` is a valid `[u32; 4]`.
193        let x: [u32; I32_4.len()] = unsafe { transmute_neo(I32_4) };
194        assert_eq!(x, U32_4);
195    }
196
197    #[test]
198    fn from_ne_bytes() {
199        // SAFETY: every `[u8; 4]` is a valid `u32`.
200        let x: [u32; U8_8.len() / 4] = unsafe { transmute_neo(U8_8) };
201        assert_eq!(x, U32_2);
202    }
203
204    #[test]
205    fn to_ne_bytes() {
206        // SAFETY: every `u32` is a valid `[u8; 4]`.
207        let y: [u8; U32_2.len() * 4] = unsafe { transmute_neo(U32_2) };
208        assert_eq!(y, U8_8);
209    }
210}