Skip to content

Commit a5b5927

Browse files
committed
Add transmute_ref! macro
This macro is like the existing `transmute!`, but it transmutes immutable references rather than values. Issue #159
1 parent 0a84008 commit a5b5927

File tree

81 files changed

+2266
-3
lines changed

Some content is hidden

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

81 files changed

+2266
-3
lines changed

src/lib.rs

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,6 +1674,140 @@ macro_rules! transmute {
16741674
}}
16751675
}
16761676

1677+
/// Safely transmutes a mutable or immutable reference of one type to an
1678+
/// immutable reference of another type of the same size.
1679+
///
1680+
/// The expression `$e` must have a concrete type, `&T` or `&mut T`, where `T:
1681+
/// Sized + AsBytes`. The `transmute_ref!` expression must also have a concrete
1682+
/// type, `&U` (`U` is inferred from the calling context), where `U: Sized +
1683+
/// FromBytes`. It must be the case that `align_of::<T>() >= align_of::<U>()`.
1684+
///
1685+
/// The lifetime of the input type, `&T` or `&mut T`, must be the same as or
1686+
/// outlive the lifetime of the output type, `&U`.
1687+
///
1688+
/// # Alignment increase error message
1689+
///
1690+
/// Because of limitations on macros, the error message generated when
1691+
/// `transmute_ref!` is used to transmute from a type of lower alignment to a
1692+
/// type of higher alignment is somewhat confusing. For example, the following
1693+
/// code:
1694+
///
1695+
/// ```rust,compile_fail
1696+
/// const INCREASE_ALIGNMENT: &u16 = zerocopy::transmute_ref!(&[0u8; 2]);
1697+
/// ```
1698+
///
1699+
/// ...generates the following error:
1700+
///
1701+
/// ```text
1702+
/// error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
1703+
/// --> src/lib.rs:1524:34
1704+
/// |
1705+
/// 5 | const INCREASE_ALIGNMENT: &u16 = zerocopy::transmute_ref!(&[0u8; 2]);
1706+
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1707+
/// |
1708+
/// = note: source type: `MaxAlignsOf<[u8; 2], u16>` (16 bits)
1709+
/// = note: target type: `AlignOf<[u8; 2]>` (8 bits)
1710+
/// = note: this error originates in the macro `zerocopy::transmute_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
1711+
/// ```
1712+
///
1713+
/// This is saying that `max(align_of::<T>(), align_of::<U>()) !=
1714+
/// align_of::<T>()`, which is equivalent to `align_of::<T>() <
1715+
/// align_of::<U>()`.
1716+
#[macro_export]
1717+
macro_rules! transmute_ref {
1718+
($e:expr) => {{
1719+
// NOTE: This must be a macro (rather than a function with trait bounds)
1720+
// because there's no way, in a generic context, to enforce that two
1721+
// types have the same size or alignment.
1722+
1723+
// Reborrow so that mutable references are supported too.
1724+
//
1725+
// In the rest of the comments, we refer only to `&T` since this
1726+
// reborrow ensures that `e` is an immutable reference.
1727+
let e = &*$e;
1728+
1729+
#[allow(unused, clippy::diverging_sub_expression)]
1730+
if false {
1731+
// This branch, though never taken, ensures that the type of `e` is
1732+
// `&T` where `T: 't + Sized + AsBytes`, that the type of this macro
1733+
// expression is `&U` where `U: 'u + Sized + FromBytes`, and that
1734+
// `'t` outlives `'u`.
1735+
const fn transmute<'u, 't: 'u, T: 't + Sized + $crate::AsBytes, U: 'u + Sized + $crate::FromBytes>(_t: &'t T) -> &'u U {
1736+
unreachable!()
1737+
}
1738+
transmute(e)
1739+
} else if false {
1740+
// This branch, though never taken, ensures that `size_of::<T>() ==
1741+
// size_of::<U>()`.
1742+
1743+
// `t` is inferred to have type `T` because it's assigned to `e` (of
1744+
// type `&T`) as `&t`.
1745+
let mut t = unreachable!();
1746+
e = &t;
1747+
1748+
// `u` is inferred to have type `U` because it's used as `&u` as the
1749+
// value returned from this branch.
1750+
//
1751+
// SAFETY: This code is never run.
1752+
let u = unsafe { $crate::macro_util::core_reexport::mem::transmute(t) };
1753+
&u
1754+
} else if false {
1755+
// This branch, though never taken, ensures that the alignment of
1756+
// `T` is greater than or equal to to the alignment of `U`.
1757+
1758+
// `t` is inferred to have type `T` because it's assigned to `e` (of
1759+
// type `&T`) as `&t`.
1760+
let mut t = unreachable!();
1761+
e = &t;
1762+
1763+
// `u` is inferred to have type `U` because it's used as `&u` as the
1764+
// value returned from this branch.
1765+
let mut u = unreachable!();
1766+
1767+
// `max_aligns` is inferred to have type `MaxAlignsOf<T, U>` because
1768+
// of the inferred types of `t` and `u`.
1769+
let max_aligns = $crate::macro_util::MaxAlignsOf::new(t, u);
1770+
// The type wildcard in this bound is inferred to be `T` because
1771+
// `align_of.into_t()` is assigned to `t` (which has type `T`).
1772+
//
1773+
// This transmute will only compile successfully if
1774+
// `max(align_of::<T>(), align_of::<U>()) == align_of::<T>()` - in
1775+
// other words, if `align_of::<T>() >= align_of::<U>()`.
1776+
//
1777+
// SAFETY: This code is never run.
1778+
let align_of: $crate::macro_util::AlignOf<_> = unsafe { $crate::macro_util::core_reexport::mem::transmute(max_aligns) };
1779+
t = align_of.into_t();
1780+
1781+
&u
1782+
} else {
1783+
// SAFETY:
1784+
// - We know that the input and output types are both `Sized` (ie,
1785+
// thin) references thanks to the trait bounds on `transmute`
1786+
// above, and thanks to the fact that transmute takes and returns
1787+
// references.
1788+
// - We know that it is sound to view the target type of the input
1789+
// reference (`T`) as the target type of the output reference
1790+
// (`U`) because `T: AsBytes` and `U: FromBytes` (guaranteed by
1791+
// trait bounds on `transmute`) and because `size_of::<T>() ==
1792+
// size_of::<U>()` (guaranteed by the first `core::mem::transmute`
1793+
// above).
1794+
// - We know that alignment is not increased thanks to the second
1795+
// `core::mem::transmute` above (the one which transmutes
1796+
// `MaxAlignsOf` into `AlignOf`).
1797+
//
1798+
// We use this reexport of `core::mem::transmute` because we know it
1799+
// will always be available for crates which are using the 2015
1800+
// edition of Rust. By contrast, if we were to use
1801+
// `std::mem::transmute`, this macro would not work for such crates
1802+
// in `no_std` contexts, and if we were to use
1803+
// `core::mem::transmute`, this macro would not work in `std`
1804+
// contexts in which `core` was not manually imported. This is not a
1805+
// problem for 2018 edition crates.
1806+
unsafe { $crate::macro_util::core_reexport::mem::transmute(e) }
1807+
}
1808+
}}
1809+
}
1810+
16771811
/// A typed reference derived from a byte slice.
16781812
///
16791813
/// A `Ref<B, T>` is a reference to a `T` which is stored in a byte slice, `B`.
@@ -3810,6 +3944,92 @@ mod tests {
38103944
assert_eq!(X, ARRAY_OF_ARRAYS);
38113945
}
38123946

3947+
#[test]
3948+
fn test_align_of() {
3949+
macro_rules! test {
3950+
($ty:ty) => {
3951+
assert_eq!(mem::size_of::<macro_util::AlignOf<$ty>>(), mem::align_of::<$ty>());
3952+
};
3953+
}
3954+
3955+
test!(());
3956+
test!(u8);
3957+
test!(AU64);
3958+
test!([AU64; 2]);
3959+
}
3960+
3961+
#[test]
3962+
fn test_max_aligns_of() {
3963+
macro_rules! test {
3964+
($t:ty, $u:ty) => {
3965+
assert_eq!(
3966+
mem::size_of::<macro_util::MaxAlignsOf<$t, $u>>(),
3967+
core::cmp::max(mem::align_of::<$t>(), mem::align_of::<$u>())
3968+
);
3969+
};
3970+
}
3971+
3972+
test!(u8, u8);
3973+
test!(u8, AU64);
3974+
test!(AU64, u8);
3975+
}
3976+
3977+
#[test]
3978+
fn test_typed_align_check() {
3979+
// Test that the type-based alignment check used in `transmute_ref!`
3980+
// behaves as expected.
3981+
3982+
macro_rules! assert_t_align_gteq_u_align {
3983+
($t:ty, $u:ty, $gteq:expr) => {
3984+
assert_eq!(
3985+
mem::size_of::<macro_util::MaxAlignsOf<$t, $u>>()
3986+
== mem::size_of::<macro_util::AlignOf<$t>>(),
3987+
$gteq
3988+
);
3989+
};
3990+
}
3991+
3992+
assert_t_align_gteq_u_align!(u8, u8, true);
3993+
assert_t_align_gteq_u_align!(AU64, AU64, true);
3994+
assert_t_align_gteq_u_align!(AU64, u8, true);
3995+
assert_t_align_gteq_u_align!(u8, AU64, false);
3996+
}
3997+
3998+
#[test]
3999+
fn test_transmute_ref() {
4000+
// Test that memory is transmuted as expected.
4001+
let array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7];
4002+
let array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]];
4003+
let x: &[[u8; 2]; 4] = transmute_ref!(&array_of_u8s);
4004+
assert_eq!(*x, array_of_arrays);
4005+
let x: &[u8; 8] = transmute_ref!(&array_of_arrays);
4006+
assert_eq!(*x, array_of_u8s);
4007+
4008+
// Test that `transmute_ref!` is legal in a const context.
4009+
const ARRAY_OF_U8S: [u8; 8] = [0u8, 1, 2, 3, 4, 5, 6, 7];
4010+
const ARRAY_OF_ARRAYS: [[u8; 2]; 4] = [[0, 1], [2, 3], [4, 5], [6, 7]];
4011+
#[allow(clippy::redundant_static_lifetimes)]
4012+
const X: &'static [[u8; 2]; 4] = transmute_ref!(&ARRAY_OF_U8S);
4013+
assert_eq!(*X, ARRAY_OF_ARRAYS);
4014+
4015+
// Test that it's legal to transmute a reference while shrinking the
4016+
// lifetime (note that `X` has the lifetime `'static`).
4017+
let x: &[u8; 8] = transmute_ref!(X);
4018+
assert_eq!(*x, ARRAY_OF_U8S);
4019+
4020+
// Test that `transmute_ref!` supports decreasing alignment.
4021+
let u = AU64(0);
4022+
let array = [0, 0, 0, 0, 0, 0, 0, 0];
4023+
let x: &[u8; 8] = transmute_ref!(&u);
4024+
assert_eq!(*x, array);
4025+
4026+
// Test that a mutable reference can be turned into an immutable one.
4027+
let mut x = 0u8;
4028+
#[allow(clippy::useless_transmute)]
4029+
let y: &u8 = transmute_ref!(&mut x);
4030+
assert_eq!(*y, 0);
4031+
}
4032+
38134033
#[test]
38144034
fn test_address() {
38154035
// Test that the `Deref` and `DerefMut` implementations return a

src/macro_util.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
1414
#![allow(missing_debug_implementations)]
1515

16-
use core::marker::PhantomData;
16+
use core::{marker::PhantomData, mem::ManuallyDrop};
1717

1818
/// A compile-time check that should be one particular value.
1919
pub trait ShouldBe<const VALUE: bool> {}
@@ -23,6 +23,40 @@ pub struct HasPadding<T: ?Sized, const VALUE: bool>(PhantomData<T>);
2323

2424
impl<T: ?Sized, const VALUE: bool> ShouldBe<VALUE> for HasPadding<T, VALUE> {}
2525

26+
/// A type whose size is equal to `align_of::<T>()`.
27+
#[repr(C)]
28+
pub struct AlignOf<T> {
29+
// This field ensures that:
30+
// - The size is always at least 1 (the minimum possible alignment).
31+
// - If the alignment is greater than 1, Rust has to round up to the next
32+
// multiple of it in order to make sure that `Align`'s size is a multiple
33+
// of that alignment. Without this field, its size could be 0, which is a
34+
// valid multiple of any alignment.
35+
_u: u8,
36+
_a: [T; 0],
37+
}
38+
39+
impl<T> AlignOf<T> {
40+
#[inline(never)] // Make `missing_inline_in_public_items` happy.
41+
pub fn into_t(self) -> T {
42+
unreachable!()
43+
}
44+
}
45+
46+
/// A type whose size is equal to `max(align_of::<T>(), align_of::<U>())`.
47+
#[repr(C)]
48+
pub union MaxAlignsOf<T, U> {
49+
_t: ManuallyDrop<AlignOf<T>>,
50+
_u: ManuallyDrop<AlignOf<U>>,
51+
}
52+
53+
impl<T, U> MaxAlignsOf<T, U> {
54+
#[inline(never)] // Make `missing_inline_in_public_items` happy.
55+
pub fn new(_t: T, _u: U) -> MaxAlignsOf<T, U> {
56+
unreachable!()
57+
}
58+
}
59+
2660
/// Does the struct type `$t` have padding?
2761
///
2862
/// `$ts` is the list of the type of every field in `$t`. `$t` must be a
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-ref-alignment-increase.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-ref-alignment-increase.rs:15:35
3+
|
4+
15 | const INCREASE_ALIGNMENT: &AU16 = transmute_ref!(&[0u8; 2]);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: source type: `MaxAlignsOf<[u8; 2], AU16>` (16 bits)
8+
= note: target type: `AlignOf<[u8; 2]>` (8 bits)
9+
= note: this error originates in the macro `transmute_ref` (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-ref-dst-mutable.rs
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
error[E0308]: mismatched types
2+
--> tests/ui-msrv/transmute-ref-dst-mutable.rs:14:22
3+
|
4+
14 | let _: &mut u8 = transmute_ref!(&0u8);
5+
| ^^^^^^^^^^^^^^^^^^^^ types differ in mutability
6+
|
7+
= note: expected mutable reference `&mut u8`
8+
found reference `&_`
9+
= note: this error originates in the macro `transmute_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
10+
11+
error[E0308]: mismatched types
12+
--> tests/ui-msrv/transmute-ref-dst-mutable.rs:14:22
13+
|
14+
14 | let _: &mut u8 = transmute_ref!(&0u8);
15+
| ^^^^^^^^^^^^^^^^^^^^ types differ in mutability
16+
|
17+
= note: expected mutable reference `&mut u8`
18+
found reference `&_`
19+
= note: this error originates in the macro `transmute_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
20+
21+
error[E0308]: mismatched types
22+
--> tests/ui-msrv/transmute-ref-dst-mutable.rs:14:22
23+
|
24+
14 | let _: &mut u8 = transmute_ref!(&0u8);
25+
| ^^^^^^^^^^^^^^^^^^^^ types differ in mutability
26+
|
27+
= note: expected mutable reference `&mut u8`
28+
found reference `&_`
29+
= note: this error originates in the macro `transmute_ref` (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-ref-dst-not-a-reference.rs
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
error[E0308]: mismatched types
2+
--> tests/ui-msrv/transmute-ref-dst-not-a-reference.rs:13:36
3+
|
4+
13 | const DST_NOT_A_REFERENCE: usize = transmute_ref!(&0u8);
5+
| ^^^^^^^^^^^^^^^^^^^^ expected `usize`, found reference
6+
|
7+
= note: expected type `usize`
8+
found reference `&_`
9+
= note: this error originates in the macro `transmute_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
10+
11+
error[E0308]: mismatched types
12+
--> tests/ui-msrv/transmute-ref-dst-not-a-reference.rs:13:36
13+
|
14+
13 | const DST_NOT_A_REFERENCE: usize = transmute_ref!(&0u8);
15+
| ^^^^^^^^^^^^^^^^^^^^ expected `usize`, found reference
16+
|
17+
= note: expected type `usize`
18+
found reference `&_`
19+
= note: this error originates in the macro `transmute_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
20+
21+
error[E0308]: mismatched types
22+
--> tests/ui-msrv/transmute-ref-dst-not-a-reference.rs:13:36
23+
|
24+
13 | const DST_NOT_A_REFERENCE: usize = transmute_ref!(&0u8);
25+
| ^^^^^^^^^^^^^^^^^^^^ expected `usize`, found reference
26+
|
27+
= note: expected type `usize`
28+
found reference `&_`
29+
= note: this error originates in the macro `transmute_ref` (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-ref-dst-not-frombytes.rs
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0277]: the trait bound `NotZerocopy: FromBytes` is not satisfied
2+
--> tests/ui-msrv/transmute-ref-dst-not-frombytes.rs:14:42
3+
|
4+
14 | const DST_NOT_FROM_BYTES: &NotZerocopy = transmute_ref!(&AU16(0));
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromBytes` is not implemented for `NotZerocopy`
6+
|
7+
note: required by a bound in `DST_NOT_FROM_BYTES::transmute`
8+
--> tests/ui-msrv/transmute-ref-dst-not-frombytes.rs:14:42
9+
|
10+
14 | const DST_NOT_FROM_BYTES: &NotZerocopy = transmute_ref!(&AU16(0));
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `DST_NOT_FROM_BYTES::transmute`
12+
= note: this error originates in the macro `transmute_ref` (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-ref-dst-unsized.rs

0 commit comments

Comments
 (0)