Skip to content

Commit 4661929

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 4661929

File tree

152 files changed

+2961
-399
lines changed

Some content is hidden

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

152 files changed

+2961
-399
lines changed

src/lib.rs

Lines changed: 147 additions & 38 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>()) !=
@@ -2011,7 +2011,8 @@ macro_rules! transmute_ref {
20112011
transmute(e)
20122012
} else if false {
20132013
// This branch, though never taken, ensures that `size_of::<T>() ==
2014-
// size_of::<U>()`.
2014+
// size_of::<U>()` and that that `align_of::<T>() >=
2015+
// align_of::<U>()`.
20152016

20162017
// `t` is inferred to have type `T` because it's assigned to `e` (of
20172018
// type `&T`) as `&t`.
@@ -2020,43 +2021,123 @@ macro_rules! transmute_ref {
20202021

20212022
// `u` is inferred to have type `U` because it's used as `&u` as the
20222023
// value returned from this branch.
2024+
let u;
2025+
2026+
$crate::assert_size_eq!(t, u);
2027+
$crate::assert_align_gt_eq!(t, u);
2028+
2029+
&u
2030+
} else {
2031+
// SAFETY:
2032+
// - We know that the input and output types are both `Sized` (ie,
2033+
// thin) references thanks to the trait bounds on `transmute`
2034+
// above, and thanks to the fact that transmute takes and returns
2035+
// references.
2036+
// - We know that it is sound to view the target type of the input
2037+
// reference (`T`) as the target type of the output reference
2038+
// (`U`) because `T: AsBytes` and `U: FromBytes` (guaranteed by
2039+
// trait bounds on `transmute`) and because `size_of::<T>() ==
2040+
// size_of::<U>()` (guaranteed by the `assert_size_eq` above).
2041+
// - We know that alignment is not increased thanks to the
2042+
// `assert_align_gt_eq` above.
20232043
//
2024-
// SAFETY: This code is never run.
2025-
let u = unsafe {
2026-
// Clippy: It's okay to transmute a type to itself.
2044+
// We use this reexport of `core::mem::transmute` because we know it
2045+
// will always be available for crates which are using the 2015
2046+
// edition of Rust. By contrast, if we were to use
2047+
// `std::mem::transmute`, this macro would not work for such crates
2048+
// in `no_std` contexts, and if we were to use
2049+
// `core::mem::transmute`, this macro would not work in `std`
2050+
// contexts in which `core` was not manually imported. This is not a
2051+
// problem for 2018 edition crates.
2052+
unsafe {
2053+
// Clippy: It's okay to transmute a type to itself.
20272054
#[allow(clippy::useless_transmute)]
2028-
$crate::macro_util::core_reexport::mem::transmute(t)
2029-
};
2030-
&u
2055+
$crate::macro_util::core_reexport::mem::transmute(e)
2056+
}
2057+
}
2058+
}}
2059+
}
2060+
2061+
/// Safely transmutes a mutable reference of one type to an mutable reference of
2062+
/// another type of the same size.
2063+
///
2064+
/// The expression `$e` must have a concrete type, `&mut T`, where `T: Sized +
2065+
/// AsBytes`. The `transmute_mut!` expression must also have a concrete type,
2066+
/// `&mut U` (`U` is inferred from the calling context), where `U: Sized +
2067+
/// FromBytes`. It must be the case that `align_of::<T>() >= align_of::<U>()`.
2068+
///
2069+
/// The lifetime of the input type, `&mut T`, must be the same as or outlive the
2070+
/// lifetime of the output type, `&mut U`.
2071+
///
2072+
/// # Alignment increase error message
2073+
///
2074+
/// Because of limitations on macros, the error message generated when
2075+
/// `transmute_mut!` is used to transmute from a type of lower alignment to a
2076+
/// type of higher alignment is somewhat confusing. For example, the following
2077+
/// code:
2078+
///
2079+
/// ```compile_fail
2080+
/// const INCREASE_ALIGNMENT: &mut u16 = zerocopy::transmute_mut!(&mut [0u8; 2]);
2081+
/// ```
2082+
///
2083+
/// ...generates the following error:
2084+
///
2085+
/// ```text
2086+
/// error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
2087+
/// --> src/lib.rs:1524:34
2088+
/// |
2089+
/// 5 | const INCREASE_ALIGNMENT: &mut u16 = zerocopy::transmute_mut!(&mut [0u8; 2]);
2090+
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2091+
/// |
2092+
/// = note: source type: `AlignOf<[u8; 2]>` (8 bits)
2093+
/// = note: target type: `MaxAlignsOf<[u8; 2], u16>` (16 bits)
2094+
/// = 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)
2095+
/// ```
2096+
///
2097+
/// This is saying that `max(align_of::<T>(), align_of::<U>()) !=
2098+
/// align_of::<T>()`, which is equivalent to `align_of::<T>() <
2099+
/// align_of::<U>()`.
2100+
#[macro_export]
2101+
macro_rules! transmute_mut {
2102+
($e:expr) => {{
2103+
// NOTE: This must be a macro (rather than a function with trait bounds)
2104+
// because there's no way, in a generic context, to enforce that two
2105+
// types have the same size or alignment.
2106+
2107+
let e = $e;
2108+
2109+
#[allow(unused, clippy::diverging_sub_expression)]
2110+
if false {
2111+
// This branch, though never taken, ensures that the type of `e` is
2112+
// `&mut T` where `T: 't + Sized + AsBytes + FromBytes`, that the
2113+
// type of this macro expression is `&mut U` where `U: 'u + Sized +
2114+
// AsBytes + FromBytes`.
2115+
fn transmute<'t, T, U>(_t: &'t mut T) -> &'t mut U
2116+
where
2117+
T: 't + Sized + $crate::AsBytes + $crate::FromBytes,
2118+
U: 't + Sized + $crate::AsBytes + $crate::FromBytes,
2119+
{
2120+
loop {}
2121+
}
2122+
transmute(e)
20312123
} else if false {
2032-
// This branch, though never taken, ensures that the alignment of
2033-
// `T` is greater than or equal to to the alignment of `U`.
2124+
// This branch, though never taken, ensures that `size_of::<T>() ==
2125+
// size_of::<U>()` and that that `align_of::<T>() >=
2126+
// align_of::<U>()`.
20342127

20352128
// `t` is inferred to have type `T` because it's assigned to `e` (of
2036-
// type `&T`) as `&t`.
2129+
// type `&mut T`) as `&mut t`.
20372130
let mut t = unreachable!();
2038-
e = &t;
2131+
e = &mut t;
20392132

2040-
// `u` is inferred to have type `U` because it's used as `&u` as the
2041-
// value returned from this branch.
2042-
let mut u = unreachable!();
2043-
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) };
2133+
// `u` is inferred to have type `U` because it's used as `&mut u` as
2134+
// the value returned from this branch.
2135+
let u;
20582136

2059-
&u
2137+
$crate::assert_size_eq!(t, u);
2138+
$crate::assert_align_gt_eq!(t, u);
2139+
2140+
&mut u
20602141
} else {
20612142
// SAFETY:
20622143
// - We know that the input and output types are both `Sized` (ie,
@@ -2065,13 +2146,12 @@ macro_rules! transmute_ref {
20652146
// references.
20662147
// - We know that it is sound to view the target type of the input
20672148
// reference (`T`) as the target type of the output reference
2068-
// (`U`) because `T: AsBytes` and `U: FromBytes` (guaranteed by
2069-
// 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`).
2149+
// (`U`) and visa versa because `T: AsBytes + FromBytes` and `U:
2150+
// AsBytes + FromBytes` (guaranteed by trait bounds on
2151+
// `transmute`) and because `size_of::<T>() == size_of::<U>()`
2152+
// (guaranteed by the `assert_size_eq` above).
2153+
// - We know that alignment is not increased thanks to the
2154+
// `assert_align_gt_eq` above.
20752155
//
20762156
// We use this reexport of `core::mem::transmute` because we know it
20772157
// will always be available for crates which are using the 2015
@@ -4301,6 +4381,35 @@ mod tests {
43014381
assert_eq!(*y, 0);
43024382
}
43034383

4384+
#[test]
4385+
fn test_transmute_mut() {
4386+
// Test that memory is transmuted as expected.
4387+
let mut array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7];
4388+
let mut array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]];
4389+
let x: &mut [[u8; 2]; 4] = transmute_mut!(&mut array_of_u8s);
4390+
assert_eq!(*x, array_of_arrays);
4391+
let x: &mut [u8; 8] = transmute_mut!(&mut array_of_arrays);
4392+
assert_eq!(*x, array_of_u8s);
4393+
4394+
{
4395+
// Test that it's legal to transmute a reference while shrinking the
4396+
// lifetime.
4397+
let x: &mut [u8; 8] = transmute_mut!(&mut array_of_arrays);
4398+
assert_eq!(*x, array_of_u8s);
4399+
}
4400+
// Test that `transmute_mut!` supports decreasing alignment.
4401+
let mut u = AU64(0);
4402+
let array = [0, 0, 0, 0, 0, 0, 0, 0];
4403+
let x: &[u8; 8] = transmute_mut!(&mut u);
4404+
assert_eq!(*x, array);
4405+
4406+
// Test that a mutable reference can be turned into an immutable one.
4407+
let mut x = 0u8;
4408+
#[allow(clippy::useless_transmute)]
4409+
let y: &u8 = transmute_mut!(&mut x);
4410+
assert_eq!(*y, 0);
4411+
}
4412+
43044413
#[test]
43054414
fn test_macros_evaluate_args_once() {
43064415
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
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
2+
--> tests/ui-msrv/transmute-mut-alignment-increase.rs:19:39
3+
|
4+
19 | const INCREASE_ALIGNMENT: &mut AU16 = transmute_mut!(&mut [0u8; 2]);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: source type: `AlignOf<[u8; 2]>` (8 bits)
8+
= note: target type: `MaxAlignsOf<[u8; 2], AU16>` (16 bits)
9+
= note: this error originates in the macro `$crate::assert_align_gt_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
10+
11+
error[E0658]: mutable references are not allowed in constants
12+
--> tests/ui-msrv/transmute-mut-alignment-increase.rs:19:54
13+
|
14+
19 | const INCREASE_ALIGNMENT: &mut AU16 = transmute_mut!(&mut [0u8; 2]);
15+
| ^^^^^^^^^^^^^
16+
|
17+
= note: see issue #57349 <https://github.com/rust-lang/rust/issues/57349> for more information
18+
19+
error[E0015]: cannot call non-const fn `INCREASE_ALIGNMENT::transmute::<[u8; 2], AU16>` in constants
20+
--> tests/ui-msrv/transmute-mut-alignment-increase.rs:19:39
21+
|
22+
19 | const INCREASE_ALIGNMENT: &mut AU16 = transmute_mut!(&mut [0u8; 2]);
23+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
24+
|
25+
= note: calls in constants are limited to constant functions, tuple structs and tuple variants
26+
= note: this error originates in the macro `transmute_mut` (in Nightly builds, run with -Z macro-backtrace for more info)
27+
28+
error[E0716]: temporary value dropped while borrowed
29+
--> tests/ui-msrv/transmute-mut-alignment-increase.rs:19:59
30+
|
31+
19 | const INCREASE_ALIGNMENT: &mut AU16 = transmute_mut!(&mut [0u8; 2]);
32+
| --------------------^^^^^^^^-
33+
| | |
34+
| | creates a temporary which is freed while still in use
35+
| temporary value is freed at the end of this statement
36+
| using this value as a constant requires that borrow lasts for `'static`

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: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
error: taking a mutable reference to a `const` item
2+
--> tests/ui-msrv/transmute-mut-const.rs:20:57
3+
|
4+
20 | const INCREASE_ALIGNMENT: &mut [u8; 2] = transmute_mut!(&mut ARRAY_OF_U8S);
5+
| ^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `-D const-item-mutation` implied by `-D warnings`
8+
= note: each usage of a `const` item creates a new temporary
9+
= note: the mutable reference will refer to this temporary, not the original `const` item
10+
note: `const` item defined here
11+
--> tests/ui-msrv/transmute-mut-const.rs:17:1
12+
|
13+
17 | const ARRAY_OF_U8S: [u8; 2] = [0u8; 2];
14+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15+
16+
error[E0658]: mutable references are not allowed in constants
17+
--> tests/ui-msrv/transmute-mut-const.rs:20:57
18+
|
19+
20 | const INCREASE_ALIGNMENT: &mut [u8; 2] = transmute_mut!(&mut ARRAY_OF_U8S);
20+
| ^^^^^^^^^^^^^^^^^
21+
|
22+
= note: see issue #57349 <https://github.com/rust-lang/rust/issues/57349> for more information
23+
24+
error[E0015]: cannot call non-const fn `INCREASE_ALIGNMENT::transmute::<[u8; 2], [u8; 2]>` in constants
25+
--> tests/ui-msrv/transmute-mut-const.rs:20:42
26+
|
27+
20 | const INCREASE_ALIGNMENT: &mut [u8; 2] = transmute_mut!(&mut ARRAY_OF_U8S);
28+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
29+
|
30+
= note: calls in constants are limited to constant functions, tuple structs and tuple variants
31+
= note: this error originates in the macro `transmute_mut` (in Nightly builds, run with -Z macro-backtrace for more info)
32+
33+
error[E0716]: temporary value dropped while borrowed
34+
--> tests/ui-msrv/transmute-mut-const.rs:20:62
35+
|
36+
20 | const INCREASE_ALIGNMENT: &mut [u8; 2] = transmute_mut!(&mut ARRAY_OF_U8S);
37+
| --------------------^^^^^^^^^^^^-
38+
| | |
39+
| | creates a temporary which is freed while still in use
40+
| temporary value is freed at the end of this statement
41+
| using this value as a constant requires that borrow lasts for `'static`
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

0 commit comments

Comments
 (0)