Skip to content

Commit dccd6f5

Browse files
committed
Add transmute_mut! macro
This macro is like the existing `transmute!`, but it transmutes mutable references rather than values. Issue #159
1 parent 3a0f0c2 commit dccd6f5

File tree

105 files changed

+1431
-116
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+1431
-116
lines changed

src/lib.rs

Lines changed: 168 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1980,7 +1980,7 @@ macro_rules! transmute {
19801980
/// |
19811981
/// = note: source type: `AlignOf<[u8; 2]>` (8 bits)
19821982
/// = note: target type: `MaxAlignsOf<[u8; 2], u16>` (16 bits)
1983-
/// = note: this error originates in the macro `zerocopy::transmute_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
1983+
/// = note: this error originates in the macro `$crate::assert_align_gt_eq` which comes from the expansion of the macro `transmute_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
19841984
/// ```
19851985
///
19861986
/// This is saying that `max(align_of::<T>(), align_of::<U>()) !=
@@ -2020,13 +2020,10 @@ macro_rules! transmute_ref {
20202020

20212021
// `u` is inferred to have type `U` because it's used as `&u` as the
20222022
// value returned from this branch.
2023-
//
2024-
// SAFETY: This code is never run.
2025-
let u = unsafe {
2026-
// Clippy: It's okay to transmute a type to itself.
2027-
#[allow(clippy::useless_transmute)]
2028-
$crate::macro_util::core_reexport::mem::transmute(t)
2029-
};
2023+
let u;
2024+
2025+
$crate::assert_size_eq!(t, u);
2026+
20302027
&u
20312028
} else if false {
20322029
// This branch, though never taken, ensures that the alignment of
@@ -2041,20 +2038,7 @@ macro_rules! transmute_ref {
20412038
// value returned from this branch.
20422039
let mut u = unreachable!();
20432040

2044-
// The type wildcard in this bound is inferred to be `T` because
2045-
// `align_of.into_t()` is assigned to `t` (which has type `T`).
2046-
let align_of: $crate::macro_util::AlignOf<_> = unreachable!();
2047-
t = align_of.into_t();
2048-
// `max_aligns` is inferred to have type `MaxAlignsOf<T, U>` because
2049-
// of the inferred types of `t` and `u`.
2050-
let mut max_aligns = $crate::macro_util::MaxAlignsOf::new(t, u);
2051-
2052-
// This transmute will only compile successfully if
2053-
// `align_of::<T>() == max(align_of::<T>(), align_of::<U>())` - in
2054-
// other words, if `align_of::<T>() >= align_of::<U>()`.
2055-
//
2056-
// SAFETY: This code is never run.
2057-
max_aligns = unsafe { $crate::macro_util::core_reexport::mem::transmute(align_of) };
2041+
$crate::assert_align_gt_eq!(t, u);
20582042

20592043
&u
20602044
} else {
@@ -2067,11 +2051,139 @@ macro_rules! transmute_ref {
20672051
// reference (`T`) as the target type of the output reference
20682052
// (`U`) because `T: AsBytes` and `U: FromBytes` (guaranteed by
20692053
// trait bounds on `transmute`) and because `size_of::<T>() ==
2070-
// size_of::<U>()` (guaranteed by the first `core::mem::transmute`
2071-
// above).
2072-
// - We know that alignment is not increased thanks to the second
2073-
// `core::mem::transmute` above (the one which transmutes
2074-
// `MaxAlignsOf` into `AlignOf`).
2054+
// size_of::<U>()` (guaranteed by the `assert_size_eq` above).
2055+
// - We know that alignment is not increased thanks to the
2056+
// `assert_align_gt_eq` above.
2057+
//
2058+
// We use this reexport of `core::mem::transmute` because we know it
2059+
// will always be available for crates which are using the 2015
2060+
// edition of Rust. By contrast, if we were to use
2061+
// `std::mem::transmute`, this macro would not work for such crates
2062+
// in `no_std` contexts, and if we were to use
2063+
// `core::mem::transmute`, this macro would not work in `std`
2064+
// contexts in which `core` was not manually imported. This is not a
2065+
// problem for 2018 edition crates.
2066+
unsafe {
2067+
// Clippy: It's okay to transmute a type to itself.
2068+
#[allow(clippy::useless_transmute)]
2069+
$crate::macro_util::core_reexport::mem::transmute(e)
2070+
}
2071+
}
2072+
}}
2073+
}
2074+
2075+
/// Safely transmutes a mutable reference of one type to an mutable reference of
2076+
/// another type of the same size.
2077+
///
2078+
/// The expression `$e` must have a concrete type, `&mut T`, where `T: Sized +
2079+
/// AsBytes`. The `transmute_mut!` expression must also have a concrete type,
2080+
/// `&mut U` (`U` is inferred from the calling context), where `U: Sized +
2081+
/// FromBytes`. It must be the case that `align_of::<T>() >= align_of::<U>()`.
2082+
///
2083+
/// The lifetime of the input type, `&mut T`, must be the same as or outlive the
2084+
/// lifetime of the output type, `&mut U`.
2085+
///
2086+
/// # Alignment increase error message
2087+
///
2088+
/// Because of limitations on macros, the error message generated when
2089+
/// `transmute_mut!` is used to transmute from a type of lower alignment to a
2090+
/// type of higher alignment is somewhat confusing. For example, the following
2091+
/// code:
2092+
///
2093+
/// ```compile_fail
2094+
/// const INCREASE_ALIGNMENT: &mut u16 = zerocopy::transmute_mut!(&mut [0u8; 2]);
2095+
/// ```
2096+
///
2097+
/// ...generates the following error:
2098+
///
2099+
/// ```text
2100+
/// error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
2101+
/// --> src/lib.rs:1524:34
2102+
/// |
2103+
/// 5 | const INCREASE_ALIGNMENT: &mut u16 = zerocopy::transmute_mut!(&mut [0u8; 2]);
2104+
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2105+
/// |
2106+
/// = note: source type: `AlignOf<[u8; 2]>` (8 bits)
2107+
/// = note: target type: `MaxAlignsOf<[u8; 2], u16>` (16 bits)
2108+
/// = note: this error originates in the macro `$crate::assert_align_gt_eq` which comes from the expansion of the macro `transmute_mut` (in Nightly builds, run with -Z macro-backtrace for more info)
2109+
/// ```
2110+
///
2111+
/// This is saying that `max(align_of::<T>(), align_of::<U>()) !=
2112+
/// align_of::<T>()`, which is equivalent to `align_of::<T>() <
2113+
/// align_of::<U>()`.
2114+
#[macro_export]
2115+
macro_rules! transmute_mut {
2116+
($e:expr) => {{
2117+
// NOTE: This must be a macro (rather than a function with trait bounds)
2118+
// because there's no way, in a generic context, to enforce that two
2119+
// types have the same size or alignment.
2120+
2121+
// Reborrow so that mutable references are supported too.
2122+
//
2123+
// In the rest of the comments, we refer only to `&T` since this
2124+
// reborrow ensures that `e` is an immutable reference.
2125+
let e = &mut *$e;
2126+
2127+
#[allow(unused, clippy::diverging_sub_expression)]
2128+
if false {
2129+
// This branch, though never taken, ensures that the type of `e` is
2130+
// `&mut T` where `T: 't + Sized + AsBytes + FromBytes`, that the
2131+
// type of this macro expression is `&mut U` where `U: 'u + Sized +
2132+
// AsBytes + FromBytes`.
2133+
fn transmute<'t, T, U>(_t: &'t mut T) -> &'t mut U
2134+
where
2135+
T: 't + Sized + $crate::AsBytes + $crate::FromBytes,
2136+
U: 't + Sized + $crate::AsBytes + $crate::FromBytes,
2137+
{
2138+
loop {}
2139+
}
2140+
transmute(&mut *e)
2141+
} else if false {
2142+
// This branch, though never taken, ensures that `size_of::<T>() ==
2143+
// size_of::<U>()`.
2144+
2145+
// `t` is inferred to have type `T` because it's assigned to `e` (of
2146+
// type `&mut T`) as `&mut t`.
2147+
let mut t = unreachable!();
2148+
e = &mut t;
2149+
2150+
// `u` is inferred to have type `U` because it's used as `&mut u` as
2151+
// the value returned from this branch.
2152+
let u;
2153+
2154+
$crate::assert_size_eq!(t, u);
2155+
2156+
&mut u
2157+
} else if false {
2158+
// This branch, though never taken, ensures that the alignment of
2159+
// `T` is greater than or equal to to the alignment of `U`.
2160+
2161+
// `t` is inferred to have type `T` because it's assigned to `e` (of
2162+
// type `&mut T`) as `&mut t`.
2163+
let mut t = unreachable!();
2164+
e = &mut t;
2165+
2166+
// `u` is inferred to have type `U` because it's used as `&mut u` as
2167+
// the value returned from this branch.
2168+
let mut u = unreachable!();
2169+
2170+
$crate::assert_align_gt_eq!(t, u);
2171+
2172+
&mut u
2173+
} else {
2174+
// SAFETY:
2175+
// - We know that the input and output types are both `Sized` (ie,
2176+
// thin) references thanks to the trait bounds on `transmute`
2177+
// above, and thanks to the fact that transmute takes and returns
2178+
// references.
2179+
// - We know that it is sound to view the target type of the input
2180+
// reference (`T`) as the target type of the output reference
2181+
// (`U`) and visa versa because `T: AsBytes + FromBytes` and `U:
2182+
// AsBytes + FromBytes` (guaranteed by trait bounds on
2183+
// `transmute`) and because `size_of::<T>() == size_of::<U>()`
2184+
// (guaranteed by the `assert_size_eq` above).
2185+
// - We know that alignment is not increased thanks to the
2186+
// `assert_align_gt_eq` above.
20752187
//
20762188
// We use this reexport of `core::mem::transmute` because we know it
20772189
// will always be available for crates which are using the 2015
@@ -4301,6 +4413,35 @@ mod tests {
43014413
assert_eq!(*y, 0);
43024414
}
43034415

4416+
#[test]
4417+
fn test_transmute_mut() {
4418+
// Test that memory is transmuted as expected.
4419+
let mut array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7];
4420+
let mut array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]];
4421+
let x: &mut [[u8; 2]; 4] = transmute_mut!(&mut array_of_u8s);
4422+
assert_eq!(*x, array_of_arrays);
4423+
let x: &mut [u8; 8] = transmute_mut!(&mut array_of_arrays);
4424+
assert_eq!(*x, array_of_u8s);
4425+
4426+
{
4427+
// Test that it's legal to transmute a reference while shrinking the
4428+
// lifetime.
4429+
let x: &mut [u8; 8] = transmute_mut!(&mut array_of_arrays);
4430+
assert_eq!(*x, array_of_u8s);
4431+
}
4432+
// Test that `transmute_mut!` supports decreasing alignment.
4433+
let mut u = AU64(0);
4434+
let array = [0, 0, 0, 0, 0, 0, 0, 0];
4435+
let x: &[u8; 8] = transmute_mut!(&mut u);
4436+
assert_eq!(*x, array);
4437+
4438+
// Test that a mutable reference can be turned into an immutable one.
4439+
let mut x = 0u8;
4440+
#[allow(clippy::useless_transmute)]
4441+
let y: &u8 = transmute_mut!(&mut x);
4442+
assert_eq!(*y, 0);
4443+
}
4444+
43044445
#[test]
43054446
fn test_macros_evaluate_args_once() {
43064447
let mut ctr = 0;

src/macro_util.rs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,58 @@ macro_rules! union_has_padding {
101101
};
102102
}
103103

104+
/// Does `t` have alignment greater than or equal to `u`? If not, this macro
105+
/// produces a compile error. It must be invoked in a dead codepath. This is
106+
/// used in `transmute_ref!` and `transmute_mut!`.
107+
#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
108+
#[macro_export]
109+
macro_rules! assert_align_gt_eq {
110+
($t:ident, $u: ident) => {{
111+
// The comments here should be read in the context of this macro's
112+
// invocations in `transmute_ref!` and `transmute_mut!`.
113+
if false {
114+
// The type wildcard in this bound is inferred to be `T` because
115+
// `align_of.into_t()` is assigned to `t` (which has type `T`).
116+
let align_of: $crate::macro_util::AlignOf<_> = unreachable!();
117+
$t = align_of.into_t();
118+
// `max_aligns` is inferred to have type `MaxAlignsOf<T, U>` because
119+
// of the inferred types of `t` and `u`.
120+
let mut max_aligns = $crate::macro_util::MaxAlignsOf::new($t, $u);
121+
122+
// This transmute will only compile successfully if
123+
// `align_of::<T>() == max(align_of::<T>(), align_of::<U>())` - in
124+
// other words, if `align_of::<T>() >= align_of::<U>()`.
125+
//
126+
// SAFETY: This code is never run.
127+
max_aligns = unsafe { $crate::macro_util::core_reexport::mem::transmute(align_of) };
128+
} else {
129+
loop {}
130+
}
131+
}};
132+
}
133+
134+
/// Do `t` and `u` have the same size? If not, this macro produces a compile
135+
/// error. It must be invoked in a dead codepath. This is used in
136+
/// `transmute_ref!` and `transmute_mut!`.
137+
#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
138+
#[macro_export]
139+
macro_rules! assert_size_eq {
140+
($t:ident, $u: ident) => {{
141+
// The comments here should be read in the context of this macro's
142+
// invocations in `transmute_ref!` and `transmute_mut!`.
143+
if false {
144+
// SAFETY: This code is never run.
145+
$u = unsafe {
146+
// Clippy: It's okay to transmute a type to itself.
147+
#[allow(clippy::useless_transmute)]
148+
$crate::macro_util::core_reexport::mem::transmute($t)
149+
};
150+
} else {
151+
loop {}
152+
}
153+
}};
154+
}
155+
104156
pub mod core_reexport {
105157
pub mod mem {
106158
pub use core::mem::transmute;
@@ -146,8 +198,8 @@ mod tests {
146198

147199
#[test]
148200
fn test_typed_align_check() {
149-
// Test that the type-based alignment check used in `transmute_ref!`
150-
// behaves as expected.
201+
// Test that the type-based alignment check used in
202+
// `assert_align_gt_eq!` behaves as expected.
151203

152204
macro_rules! assert_t_align_gteq_u_align {
153205
($t:ty, $u:ty, $gteq:expr) => {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-alignment-increase.rs

tests/ui-msrv/transmute-mut-const.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-const.rs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-dst-generic.rs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-dst-not-a-reference.rs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-dst-not-asbytes.rs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-dst-not-frombytes.rs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-dst-unsized.rs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-illegal-lifetime.rs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-size-decrease.rs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-size-increase.rs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-src-dst-generic.rs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-src-dst-not-references.rs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-src-dst-unsized.rs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-src-generic.rs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-src-mutable.rs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-src-not-a-reference.rs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-src-not-asbytes.rs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-src-not-frombytes.rs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-mut-src-unsized.rs

tests/ui-msrv/transmute-ref-alignment-increase.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ error[E0512]: cannot transmute between types of different sizes, or dependently-
66
|
77
= note: source type: `AlignOf<[u8; 2]>` (8 bits)
88
= note: target type: `MaxAlignsOf<[u8; 2], AU16>` (16 bits)
9-
= note: this error originates in the macro `transmute_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
9+
= note: this error originates in the macro `$crate::assert_align_gt_eq` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui-msrv/transmute-ref-dst-generic.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ error[E0512]: cannot transmute between types of different sizes, or dependently-
66
|
77
= note: source type: `u8` (8 bits)
88
= note: target type: `T` (this type does not have a fixed size)
9-
= note: this error originates in the macro `transmute_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
9+
= note: this error originates in the macro `$crate::assert_size_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
1010

1111
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
1212
--> tests/ui-msrv/transmute-ref-dst-generic.rs:17:5
@@ -16,4 +16,4 @@ error[E0512]: cannot transmute between types of different sizes, or dependently-
1616
|
1717
= note: source type: `AlignOf<u8>` (8 bits)
1818
= note: target type: `MaxAlignsOf<u8, T>` (size can vary because of T)
19-
= note: this error originates in the macro `transmute_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
19+
= note: this error originates in the macro `$crate::assert_align_gt_eq` (in Nightly builds, run with -Z macro-backtrace for more info)

0 commit comments

Comments
 (0)