35
35
/// original `Src` will be forgotten, and the value of type `Dst` will be
36
36
/// returned.
37
37
///
38
+ /// # `#![allow(shrink)]`
39
+ ///
40
+ /// If `#![allow(shrink)]` is provided, `transmute!` additionally supports
41
+ /// transmutations that shrink the size of the value; e.g.:
42
+ ///
43
+ /// ```
44
+ /// # use zerocopy::transmute;
45
+ /// let u: u32 = transmute!(#![allow(shrink)] 0u64);
46
+ /// assert_eq!(u, 0u32);
47
+ /// ```
48
+ ///
38
49
/// # Examples
39
50
///
40
51
/// ```
51
62
/// This macro can be invoked in `const` contexts.
52
63
#[ macro_export]
53
64
macro_rules! transmute {
54
- ( $e: expr) => { {
55
- // NOTE: This must be a macro (rather than a function with trait bounds)
56
- // because there's no way, in a generic context, to enforce that two
57
- // types have the same size. `core::mem::transmute` uses compiler magic
58
- // to enforce this so long as the types are concrete.
65
+ // NOTE: This must be a macro (rather than a function with trait bounds)
66
+ // because there's no way, in a generic context, to enforce that two types
67
+ // have the same size. `core::mem::transmute` uses compiler magic to enforce
68
+ // this so long as the types are concrete.
69
+ ( #![ allow( shrink) ] $e: expr) => { {
70
+ let mut e = $e;
71
+ if false {
72
+ // This branch, though never taken, ensures that the type of `e` is
73
+ // `IntoBytes` and that the type of the outer macro invocation
74
+ // expression is `FromBytes`.
75
+
76
+ fn transmute<Src , Dst >( src: Src ) -> Dst
77
+ where
78
+ Src : $crate:: IntoBytes ,
79
+ Dst : $crate:: FromBytes ,
80
+ {
81
+ let _ = src;
82
+ loop { }
83
+ }
84
+ loop { }
85
+ #[ allow( unreachable_code) ]
86
+ transmute( e)
87
+ } else {
88
+ use $crate:: util:: macro_util:: core_reexport:: mem:: ManuallyDrop ;
89
+
90
+ // NOTE: `repr(packed)` is important! It ensures that the size of
91
+ // `Transmute` won't be rounded up to accomodate `Src`'s or `Dst`'s
92
+ // alignment, which would break the size comparison logic below.
93
+ //
94
+ // As an example of why this is problematic, consider `Src = [u8;
95
+ // 5]`, `Dst = u32`. The total size of `Transmute<Src, Dst>` would
96
+ // be 8, and so we would reject a `[u8; 5]` to `u32` transmute as
97
+ // being size-increasing, which it isn't.
98
+ #[ repr( C , packed) ]
99
+ union Transmute <Src , Dst > {
100
+ src: ManuallyDrop <Src >,
101
+ dst: ManuallyDrop <Dst >,
102
+ }
59
103
104
+ // SAFETY: `Transmute` is a `reper(C)` union whose `src` field has
105
+ // type `ManuallyDrop<Src>`. Thus, the `src` field starts at byte
106
+ // offset 0 within `Transmute` [1]. `ManuallyDrop<T>` has the same
107
+ // layout and bit validity as `T`, so it is sound to transmute `Src`
108
+ // to `Transmute`.
109
+ //
110
+ // [1] https://doc.rust-lang.org/1.85.0/reference/type-layout.html#reprc-unions
111
+ //
112
+ // [2] Per https://doc.rust-lang.org/1.85.0/std/mem/struct.ManuallyDrop.html:
113
+ //
114
+ // `ManuallyDrop<T>` is guaranteed to have the same layout and bit
115
+ // validity as `T`
116
+ let u: Transmute <_, _> = unsafe {
117
+ // Clippy: We can't annotate the types; this macro is designed
118
+ // to infer the types from the calling context.
119
+ #[ allow( clippy:: missing_transmute_annotations) ]
120
+ $crate:: util:: macro_util:: core_reexport:: mem:: transmute( e)
121
+ } ;
122
+
123
+ if false {
124
+ // SAFETY: This code is never executed.
125
+ e = ManuallyDrop :: into_inner( unsafe { u. src } ) ;
126
+ // Suppress the `unused_assignments` lint on the previous line.
127
+ let _ = e;
128
+ loop { }
129
+ } else {
130
+ // SAFETY: Per the safety comment on `let u` above, the `dst`
131
+ // field in `Transmute` starts at byte offset 0, and has the
132
+ // same layout and bit validity as `Dst`.
133
+ //
134
+ // Transmuting `Src` to `Transmute<Src, Dst>` above using
135
+ // `core::mem::transmute` ensures that `size_of::<Src>() ==
136
+ // size_of::<Transmute<Src, Dst>>()`. A `#[repr(C, packed)]`
137
+ // union has the maximum size of all of its fields [1], so this
138
+ // is equivalent to `size_of::<Src>() >= size_of::<Dst>()`.
139
+ //
140
+ // The outer `if`'s `false` branch ensures that `Src: IntoBytes`
141
+ // and `Dst: FromBytes`. This, combined with the size bound,
142
+ // ensures that this transmute is sound.
143
+ //
144
+ // [1] Per https://doc.rust-lang.org/1.85.0/reference/type-layout.html#reprc-unions:
145
+ //
146
+ // The union will have a size of the maximum size of all of
147
+ // its fields rounded to its alignment
148
+ let dst = unsafe { u. dst } ;
149
+ $crate:: util:: macro_util:: must_use( ManuallyDrop :: into_inner( dst) )
150
+ }
151
+ }
152
+ } } ;
153
+ ( $e: expr) => { {
60
154
let e = $e;
61
155
if false {
62
156
// This branch, though never taken, ensures that the type of `e` is
63
- // `IntoBytes` and that the type of this macro invocation expression
64
- // is `FromBytes`.
65
-
66
- struct AssertIsIntoBytes <T : $crate:: IntoBytes >( T ) ;
67
- let _ = AssertIsIntoBytes ( e) ;
157
+ // `IntoBytes` and that the type of the outer macro invocation
158
+ // expression is `FromBytes`.
68
159
69
- struct AssertIsFromBytes <U : $crate:: FromBytes >( U ) ;
70
- #[ allow( unused, unreachable_code) ]
71
- let u = AssertIsFromBytes ( loop { } ) ;
72
- u. 0
160
+ fn transmute<Src , Dst >( src: Src ) -> Dst
161
+ where
162
+ Src : $crate:: IntoBytes ,
163
+ Dst : $crate:: FromBytes ,
164
+ {
165
+ let _ = src;
166
+ loop { }
167
+ }
168
+ loop { }
169
+ #[ allow( unreachable_code) ]
170
+ transmute( e)
73
171
} else {
74
172
// SAFETY: `core::mem::transmute` ensures that the type of `e` and
75
173
// the type of this macro invocation expression have the same size.
76
174
// We know this transmute is safe thanks to the `IntoBytes` and
77
175
// `FromBytes` bounds enforced by the `false` branch.
78
- //
79
- // We use this reexport of `core::mem::transmute` because we know it
80
- // will always be available for crates which are using the 2015
81
- // edition of Rust. By contrast, if we were to use
82
- // `std::mem::transmute`, this macro would not work for such crates
83
- // in `no_std` contexts, and if we were to use
84
- // `core::mem::transmute`, this macro would not work in `std`
85
- // contexts in which `core` was not manually imported. This is not a
86
- // problem for 2018 edition crates.
87
176
let u = unsafe {
88
177
// Clippy: We can't annotate the types; this macro is designed
89
178
// to infer the types from the calling context.
@@ -92,7 +181,7 @@ macro_rules! transmute {
92
181
} ;
93
182
$crate:: util:: macro_util:: must_use( u)
94
183
}
95
- } }
184
+ } } ;
96
185
}
97
186
98
187
/// Safely transmutes a mutable or immutable reference of one type to an
@@ -1046,6 +1135,10 @@ mod tests {
1046
1135
let x: [ u8 ; 8 ] = transmute ! ( array_of_arrays) ;
1047
1136
assert_eq ! ( x, array_of_u8s) ;
1048
1137
1138
+ // Test that memory is transmuted as expected when shrinking.
1139
+ let x: [ [ u8 ; 2 ] ; 3 ] = transmute ! ( #![ allow( shrink) ] array_of_u8s) ;
1140
+ assert_eq ! ( x, [ [ 0u8 , 1 ] , [ 2 , 3 ] , [ 4 , 5 ] ] ) ;
1141
+
1049
1142
// Test that the source expression's value is forgotten rather than
1050
1143
// dropped.
1051
1144
#[ derive( IntoBytes ) ]
@@ -1058,12 +1151,16 @@ mod tests {
1058
1151
}
1059
1152
#[ allow( clippy:: let_unit_value) ]
1060
1153
let _: ( ) = transmute ! ( PanicOnDrop ( ( ) ) ) ;
1154
+ #[ allow( clippy:: let_unit_value) ]
1155
+ let _: ( ) = transmute ! ( #![ allow( shrink) ] PanicOnDrop ( ( ) ) ) ;
1061
1156
1062
1157
// Test that `transmute!` is legal in a const context.
1063
1158
const ARRAY_OF_U8S : [ u8 ; 8 ] = [ 0u8 , 1 , 2 , 3 , 4 , 5 , 6 , 7 ] ;
1064
1159
const ARRAY_OF_ARRAYS : [ [ u8 ; 2 ] ; 4 ] = [ [ 0 , 1 ] , [ 2 , 3 ] , [ 4 , 5 ] , [ 6 , 7 ] ] ;
1065
1160
const X : [ [ u8 ; 2 ] ; 4 ] = transmute ! ( ARRAY_OF_U8S ) ;
1066
1161
assert_eq ! ( X , ARRAY_OF_ARRAYS ) ;
1162
+ const X_SHRINK : [ [ u8 ; 2 ] ; 3 ] = transmute ! ( #![ allow( shrink) ] ARRAY_OF_U8S ) ;
1163
+ assert_eq ! ( X_SHRINK , [ [ 0u8 , 1 ] , [ 2 , 3 ] , [ 4 , 5 ] ] ) ;
1067
1164
1068
1165
// Test that `transmute!` works with `!Immutable` types.
1069
1166
let x: usize = transmute ! ( UnsafeCell :: new( 1usize ) ) ;
0 commit comments