Skip to content

Commit c18affd

Browse files
joshlfjswrenn
andcommitted
[macros] Support shrinking value transmutes
In `transmute!`, support an `#![allow(shrink)]` attribute which is invoked as follows: transmute!(#![allow(shrink)] src); When this attribute is provided, `transmute!` will permit shrinking transmutes, in which the destination value may be smaller than the source value. Co-authored-by: Jack Wrenn <jswrenn@amazon.com> gherrit-pr-id: I46b18b4b1d10507b7e1d2e01b09dc4960cfcdce1
1 parent 8fbfac9 commit c18affd

19 files changed

+223
-164
lines changed

src/macros.rs

Lines changed: 114 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@
3535
/// original `Src` will be forgotten, and the value of type `Dst` will be
3636
/// returned.
3737
///
38+
/// # `#![allow(shrink)]`
39+
///
40+
/// If `#![allow(shrink)]` is provided, `transmute!` additionally supports
41+
/// transmutations that shrink the size of the referent; e.g.:
42+
///
43+
/// ```
44+
/// # use zerocopy::transmute;
45+
/// let u: u32 = transmute!(#![allow(shrink)] 0u64);
46+
/// assert_eq!(u, 0u32);
47+
/// ```
48+
///
3849
/// # Examples
3950
///
4051
/// ```
@@ -49,41 +60,112 @@
4960
/// # Use in `const` contexts
5061
///
5162
/// This macro can be invoked in `const` contexts.
63+
// TODO: Update docs to document `#![allow(shrink)]`
5264
#[macro_export]
5365
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.
66+
// NOTE: This must be a macro (rather than a function with trait bounds)
67+
// because there's no way, in a generic context, to enforce that two types
68+
// have the same size. `core::mem::transmute` uses compiler magic to enforce
69+
// this so long as the types are concrete.
70+
(#![allow(shrink)] $e:expr) => {{
71+
let mut e = $e;
72+
if false {
73+
// This branch, though never taken, ensures that the type of `e` is
74+
// `IntoBytes` and that the type of the outer macro invocation
75+
// expression is `FromBytes`.
76+
77+
fn transmute<Src, Dst>(src: Src) -> Dst
78+
where
79+
Src: $crate::IntoBytes,
80+
Dst: $crate::FromBytes,
81+
{
82+
let _ = src;
83+
loop {}
84+
}
85+
loop {}
86+
#[allow(unreachable_code)]
87+
transmute(e)
88+
} else {
89+
use $crate::util::macro_util::core_reexport::mem::ManuallyDrop;
5990

91+
#[repr(C, packed)]
92+
union Transmute<Src, Dst> {
93+
src: ManuallyDrop<Src>,
94+
dst: ManuallyDrop<Dst>,
95+
}
96+
97+
// SAFETY: `Transmute` is a `reper(C)` union whose `src` field has
98+
// type `ManuallyDrop<Src>`. Thus, the `src` field starts at byte
99+
// offset 0 within `Transmute` [1]. `ManuallyDrop<T>` has the same
100+
// layout and bit validity as `T`, so it is sound to transmute `Src`
101+
// to `Transmute`.
102+
//
103+
// [1] https://doc.rust-lang.org/1.85.0/reference/type-layout.html#reprc-unions
104+
//
105+
// [2] Per https://doc.rust-lang.org/1.85.0/std/mem/struct.ManuallyDrop.html:
106+
//
107+
// `ManuallyDrop<T>` is guaranteed to have the same layout and bit
108+
// validity as `T`
109+
let u: Transmute<_, _> = unsafe {
110+
// Clippy: We can't annotate the types; this macro is designed
111+
// to infer the types from the calling context.
112+
#[allow(clippy::missing_transmute_annotations)]
113+
$crate::util::macro_util::core_reexport::mem::transmute(e)
114+
};
115+
116+
if false {
117+
// SAFETY: This code is never executed.
118+
e = ManuallyDrop::into_inner(unsafe { u.src });
119+
// Suppress the `unused_assignments` lint on the previous line.
120+
let _ = e;
121+
loop {}
122+
} else {
123+
// Per the safety comment on `let u` above, the `dst` field in
124+
// `Transmute` starts at byte offset 0, and has the same layout
125+
// and bit validity as `Dst`.
126+
//
127+
// Transmuting `Src` to `Transmute<Src, Dst>` above using
128+
// `core::mem::transmute` ensures that `size_of::<Src>() ==
129+
// size_of::<Transmute<Src, Dst>>()`. A `#[repr(C, packed)]`
130+
// union has the maximum size of all of its fields [1], so this
131+
// is equivalent to `size_of::<Src>() >= size_of::<Dst>()`.
132+
//
133+
// The outer `if`'s `false` branch ensures that `Src: IntoBytes`
134+
// and `Dst: FromBytes`. This, combined with the size bound,
135+
// ensures that this transmute is sound.
136+
//
137+
// Per https://doc.rust-lang.org/1.85.0/reference/type-layout.html#reprc-unions:
138+
//
139+
// The union will have a size of the maximum size of all of
140+
// its fields rounded to its alignment
141+
let dst = unsafe { u.dst };
142+
$crate::util::macro_util::must_use(ManuallyDrop::into_inner(dst))
143+
}
144+
}
145+
}};
146+
($e:expr) => {{
60147
let e = $e;
61148
if false {
62149
// 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);
150+
// `IntoBytes` and that the type of the outer macro invocation
151+
// expression is `FromBytes`.
68152

69-
struct AssertIsFromBytes<U: $crate::FromBytes>(U);
70-
#[allow(unused, unreachable_code)]
71-
let u = AssertIsFromBytes(loop {});
72-
u.0
153+
fn transmute<Src, Dst>(src: Src) -> Dst
154+
where
155+
Src: $crate::IntoBytes,
156+
Dst: $crate::FromBytes,
157+
{
158+
let _ = src;
159+
loop {}
160+
}
161+
loop {}
162+
#[allow(unreachable_code)]
163+
transmute(e)
73164
} else {
74165
// SAFETY: `core::mem::transmute` ensures that the type of `e` and
75166
// the type of this macro invocation expression have the same size.
76167
// We know this transmute is safe thanks to the `IntoBytes` and
77168
// `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.
87169
let u = unsafe {
88170
// Clippy: We can't annotate the types; this macro is designed
89171
// to infer the types from the calling context.
@@ -92,7 +174,7 @@ macro_rules! transmute {
92174
};
93175
$crate::util::macro_util::must_use(u)
94176
}
95-
}}
177+
}};
96178
}
97179

98180
/// Safely transmutes a mutable or immutable reference of one type to an
@@ -1046,6 +1128,10 @@ mod tests {
10461128
let x: [u8; 8] = transmute!(array_of_arrays);
10471129
assert_eq!(x, array_of_u8s);
10481130

1131+
// Test that memory is transmuted as expected when shrinking.
1132+
let x: [[u8; 2]; 3] = transmute!(#![allow(shrink)] array_of_u8s);
1133+
assert_eq!(x, [[0u8, 1], [2, 3], [4, 5]]);
1134+
10491135
// Test that the source expression's value is forgotten rather than
10501136
// dropped.
10511137
#[derive(IntoBytes)]
@@ -1058,12 +1144,16 @@ mod tests {
10581144
}
10591145
#[allow(clippy::let_unit_value)]
10601146
let _: () = transmute!(PanicOnDrop(()));
1147+
#[allow(clippy::let_unit_value)]
1148+
let _: () = transmute!(#![allow(shrink)] PanicOnDrop(()));
10611149

10621150
// Test that `transmute!` is legal in a const context.
10631151
const ARRAY_OF_U8S: [u8; 8] = [0u8, 1, 2, 3, 4, 5, 6, 7];
10641152
const ARRAY_OF_ARRAYS: [[u8; 2]; 4] = [[0, 1], [2, 3], [4, 5], [6, 7]];
10651153
const X: [[u8; 2]; 4] = transmute!(ARRAY_OF_U8S);
10661154
assert_eq!(X, ARRAY_OF_ARRAYS);
1155+
const X_SHRINK: [[u8; 2]; 3] = transmute!(#![allow(shrink)] ARRAY_OF_U8S);
1156+
assert_eq!(X_SHRINK, [[0u8, 1], [2, 3], [4, 5]]);
10671157

10681158
// Test that `transmute!` works with `!Immutable` types.
10691159
let x: usize = transmute!(UnsafeCell::new(1usize));

tests/ui-msrv/include_value_not_from_bytes.stderr

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ error[E0277]: the trait bound `NotZerocopy<u32>: zerocopy::FromBytes` is not sat
44
19 | const NOT_FROM_BYTES: NotZerocopy<u32> = include_value!("../../testdata/include_value/data");
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `zerocopy::FromBytes` is not implemented for `NotZerocopy<u32>`
66
|
7-
note: required by `AssertIsFromBytes`
7+
note: required by a bound in `NOT_FROM_BYTES::transmute`
88
--> tests/ui-msrv/include_value_not_from_bytes.rs:19:42
99
|
1010
19 | const NOT_FROM_BYTES: NotZerocopy<u32> = include_value!("../../testdata/include_value/data");
1111
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
| |
13+
| required by a bound in this
14+
| required by this bound in `NOT_FROM_BYTES::transmute`
1215
= note: this error originates in the macro `$crate::transmute` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui-msrv/transmute-dst-not-frombytes.stderr

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ error[E0277]: the trait bound `NotZerocopy: zerocopy::FromBytes` is not satisfie
44
19 | const DST_NOT_FROM_BYTES: NotZerocopy = transmute!(AU16(0));
55
| ^^^^^^^^^^^^^^^^^^^ the trait `zerocopy::FromBytes` is not implemented for `NotZerocopy`
66
|
7-
note: required by `AssertIsFromBytes`
7+
note: required by a bound in `DST_NOT_FROM_BYTES::transmute`
88
--> tests/ui-msrv/transmute-dst-not-frombytes.rs:19:41
99
|
1010
19 | const DST_NOT_FROM_BYTES: NotZerocopy = transmute!(AU16(0));
1111
| ^^^^^^^^^^^^^^^^^^^
12+
| |
13+
| required by a bound in this
14+
| required by this bound in `DST_NOT_FROM_BYTES::transmute`
1215
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui-msrv/transmute-ptr-to-usize.stderr

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,12 @@ error[E0277]: the trait bound `*const usize: IntoBytes` is not satisfied
44
20 | const POINTER_VALUE: usize = transmute!(&0usize as *const usize);
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoBytes` is not implemented for `*const usize`
66
|
7-
note: required by `AssertIsIntoBytes`
7+
note: required by a bound in `POINTER_VALUE::transmute`
88
--> tests/ui-msrv/transmute-ptr-to-usize.rs:20:30
99
|
1010
20 | const POINTER_VALUE: usize = transmute!(&0usize as *const usize);
1111
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12-
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
13-
14-
error[E0277]: the trait bound `*const usize: IntoBytes` is not satisfied
15-
--> tests/ui-msrv/transmute-ptr-to-usize.rs:20:30
16-
|
17-
20 | const POINTER_VALUE: usize = transmute!(&0usize as *const usize);
18-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoBytes` is not implemented for `*const usize`
19-
|
20-
note: required by a bound in `AssertIsIntoBytes`
21-
--> tests/ui-msrv/transmute-ptr-to-usize.rs:20:30
22-
|
23-
20 | const POINTER_VALUE: usize = transmute!(&0usize as *const usize);
24-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `AssertIsIntoBytes`
12+
| |
13+
| required by a bound in this
14+
| required by this bound in `POINTER_VALUE::transmute`
2515
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-size-increase-allow-shrink.rs
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
2+
--> tests/ui-msrv/transmute-size-increase-allow-shrink.rs:20:29
3+
|
4+
20 | const INCREASE_SIZE: AU16 = transmute!(#![allow(shrink)] 0u8);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: source type: `u8` (8 bits)
8+
= note: target type: `Transmute<u8, AU16>` (16 bits)
9+
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui-msrv/transmute-src-not-intobytes.stderr

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,12 @@ error[E0277]: the trait bound `NotZerocopy<AU16>: zerocopy::IntoBytes` is not sa
44
19 | const SRC_NOT_AS_BYTES: AU16 = transmute!(NotZerocopy(AU16(0)));
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `zerocopy::IntoBytes` is not implemented for `NotZerocopy<AU16>`
66
|
7-
note: required by `AssertIsIntoBytes`
7+
note: required by a bound in `SRC_NOT_AS_BYTES::transmute`
88
--> tests/ui-msrv/transmute-src-not-intobytes.rs:19:32
99
|
1010
19 | const SRC_NOT_AS_BYTES: AU16 = transmute!(NotZerocopy(AU16(0)));
1111
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12-
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
13-
14-
error[E0277]: the trait bound `NotZerocopy<AU16>: zerocopy::IntoBytes` is not satisfied
15-
--> tests/ui-msrv/transmute-src-not-intobytes.rs:19:32
16-
|
17-
19 | const SRC_NOT_AS_BYTES: AU16 = transmute!(NotZerocopy(AU16(0)));
18-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `zerocopy::IntoBytes` is not implemented for `NotZerocopy<AU16>`
19-
|
20-
note: required by a bound in `AssertIsIntoBytes`
21-
--> tests/ui-msrv/transmute-src-not-intobytes.rs:19:32
22-
|
23-
19 | const SRC_NOT_AS_BYTES: AU16 = transmute!(NotZerocopy(AU16(0)));
24-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `AssertIsIntoBytes`
12+
| |
13+
| required by a bound in this
14+
| required by this bound in `SRC_NOT_AS_BYTES::transmute`
2515
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui-nightly/include_value_not_from_bytes.stderr

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ error[E0277]: the trait bound `NotZerocopy<u32>: zerocopy::FromBytes` is not sat
22
--> tests/ui-nightly/include_value_not_from_bytes.rs:19:42
33
|
44
19 | const NOT_FROM_BYTES: NotZerocopy<u32> = include_value!("../../testdata/include_value/data");
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6-
| |
7-
| the trait `zerocopy::FromBytes` is not implemented for `NotZerocopy<u32>`
8-
| required by a bound introduced by this call
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `zerocopy::FromBytes` is not implemented for `NotZerocopy<u32>`
96
|
107
= note: Consider adding `#[derive(FromBytes)]` to `NotZerocopy<u32>`
118
= help: the following other types implement trait `zerocopy::FromBytes`:
@@ -18,9 +15,12 @@ error[E0277]: the trait bound `NotZerocopy<u32>: zerocopy::FromBytes` is not sat
1815
AtomicIsize
1916
AtomicU16
2017
and $N others
21-
note: required by a bound in `AssertIsFromBytes`
18+
note: required by a bound in `NOT_FROM_BYTES::transmute`
2219
--> tests/ui-nightly/include_value_not_from_bytes.rs:19:42
2320
|
2421
19 | const NOT_FROM_BYTES: NotZerocopy<u32> = include_value!("../../testdata/include_value/data");
25-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `AssertIsFromBytes`
22+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
23+
| |
24+
| required by a bound in this function
25+
| required by this bound in `transmute`
2626
= note: this error originates in the macro `$crate::transmute` which comes from the expansion of the macro `include_value` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui-nightly/transmute-dst-not-frombytes.stderr

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ error[E0277]: the trait bound `NotZerocopy: zerocopy::FromBytes` is not satisfie
22
--> tests/ui-nightly/transmute-dst-not-frombytes.rs:19:41
33
|
44
19 | const DST_NOT_FROM_BYTES: NotZerocopy = transmute!(AU16(0));
5-
| ^^^^^^^^^^^^^^^^^^^
6-
| |
7-
| the trait `zerocopy::FromBytes` is not implemented for `NotZerocopy`
8-
| required by a bound introduced by this call
5+
| ^^^^^^^^^^^^^^^^^^^ the trait `zerocopy::FromBytes` is not implemented for `NotZerocopy`
96
|
107
= note: Consider adding `#[derive(FromBytes)]` to `NotZerocopy`
118
= help: the following other types implement trait `zerocopy::FromBytes`:
@@ -18,9 +15,12 @@ error[E0277]: the trait bound `NotZerocopy: zerocopy::FromBytes` is not satisfie
1815
AtomicIsize
1916
AtomicU16
2017
and $N others
21-
note: required by a bound in `AssertIsFromBytes`
18+
note: required by a bound in `DST_NOT_FROM_BYTES::transmute`
2219
--> tests/ui-nightly/transmute-dst-not-frombytes.rs:19:41
2320
|
2421
19 | const DST_NOT_FROM_BYTES: NotZerocopy = transmute!(AU16(0));
25-
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `AssertIsFromBytes`
22+
| ^^^^^^^^^^^^^^^^^^^
23+
| |
24+
| required by a bound in this function
25+
| required by this bound in `transmute`
2626
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui-nightly/transmute-ptr-to-usize.stderr

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,12 @@ error[E0277]: the trait bound `*const usize: IntoBytes` is not satisfied
99
|
1010
= note: Consider adding `#[derive(IntoBytes)]` to `*const usize`
1111
= help: the trait `IntoBytes` is implemented for `usize`
12-
note: required by a bound in `AssertIsIntoBytes`
12+
note: required by a bound in `POINTER_VALUE::transmute`
1313
--> tests/ui-nightly/transmute-ptr-to-usize.rs:20:30
1414
|
1515
20 | const POINTER_VALUE: usize = transmute!(&0usize as *const usize);
16-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `AssertIsIntoBytes`
17-
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
18-
19-
error[E0277]: the trait bound `*const usize: IntoBytes` is not satisfied
20-
--> tests/ui-nightly/transmute-ptr-to-usize.rs:20:30
21-
|
22-
20 | const POINTER_VALUE: usize = transmute!(&0usize as *const usize);
23-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoBytes` is not implemented for `*const usize`
24-
|
25-
= note: Consider adding `#[derive(IntoBytes)]` to `*const usize`
26-
= help: the trait `IntoBytes` is implemented for `usize`
27-
note: required by a bound in `AssertIsIntoBytes`
28-
--> tests/ui-nightly/transmute-ptr-to-usize.rs:20:30
29-
|
30-
20 | const POINTER_VALUE: usize = transmute!(&0usize as *const usize);
31-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `AssertIsIntoBytes`
16+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
17+
| |
18+
| required by a bound in this function
19+
| required by this bound in `transmute`
3220
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)

0 commit comments

Comments
 (0)