Skip to content

Commit f463170

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 0390717 commit f463170

15 files changed

+2423
-36
lines changed

.github/workflows/ci.yml

+8-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,14 @@ jobs:
146146
if: ${{ contains(matrix.target, 'i686') }}
147147

148148
- name: Run tests
149-
run: cargo +${{ env.ZC_TOOLCHAIN }} test --package ${{ matrix.crate }} --target ${{ matrix.target }} ${{ matrix.features }} --verbose
149+
# We skip `ui` tests unless the `byteorder` feature is enabled (currently,
150+
# all feature sets besides `--no-default-features`). The reason is that,
151+
# when a trait impl is missing, rustc emits different suggestions for
152+
# types that implement the trait when the `byteorder` feature is enabled
153+
# than when it's disabled. Thus, any given compiler output will be
154+
# incorrect either when run with `byteorder` or without it, but no output
155+
# can be correct in both circumstances.
156+
run: cargo +${{ env.ZC_TOOLCHAIN }} test --package ${{ matrix.crate }} --target ${{ matrix.target }} ${{ matrix.features }} --verbose -- ${{ matrix.features == '--no-default-features' && '--skip ui' || '' }}
150157
# Only run tests when targetting x86 (32- or 64-bit) - we're executing on
151158
# x86_64, so we can't run tests for any non-x86 target.
152159
#

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,4 @@ static_assertions = "1.1"
5555
# and the version used in CI are guaranteed to be the same. Future versions
5656
# sometimes change the output format slightly, so a version mismatch can cause
5757
# CI test failures.
58-
trybuild = "=1.0.80"
58+
trybuild = { version = "=1.0.80", features = ["diff"] }

src/lib.rs

+262-7
Original file line numberDiff line numberDiff line change
@@ -1216,7 +1216,7 @@ impl<T> Unalign<T> {
12161216
/// Attempts to return a reference to the wrapped `T`, failing if `self` is
12171217
/// not properly aligned.
12181218
///
1219-
/// If `self` does not satisfy `mem::align_of::<T>()`, then it is unsound to
1219+
/// If `self` does not satisfy `align_of::<T>()`, then it is unsound to
12201220
/// return a reference to the wrapped `T`, and `try_deref` returns `None`.
12211221
///
12221222
/// If `T: Unaligned`, then `Unalign<T>` implements [`Deref`], and callers
@@ -1234,7 +1234,7 @@ impl<T> Unalign<T> {
12341234
/// Attempts to return a mutable reference to the wrapped `T`, failing if
12351235
/// `self` is not properly aligned.
12361236
///
1237-
/// If `self` does not satisfy `mem::align_of::<T>()`, then it is unsound to
1237+
/// If `self` does not satisfy `align_of::<T>()`, then it is unsound to
12381238
/// return a reference to the wrapped `T`, and `try_deref_mut` returns
12391239
/// `None`.
12401240
///
@@ -1257,7 +1257,7 @@ impl<T> Unalign<T> {
12571257
///
12581258
/// # Safety
12591259
///
1260-
/// If `self` does not satisfy `mem::align_of::<T>()`, then
1260+
/// If `self` does not satisfy `align_of::<T>()`, then
12611261
/// `self.deref_unchecked()` may cause undefined behavior.
12621262
pub const unsafe fn deref_unchecked(&self) -> &T {
12631263
// SAFETY: `self.get_ptr()` returns a raw pointer to a valid `T` at the
@@ -1276,7 +1276,7 @@ impl<T> Unalign<T> {
12761276
///
12771277
/// # Safety
12781278
///
1279-
/// If `self` does not satisfy `mem::align_of::<T>()`, then
1279+
/// If `self` does not satisfy `align_of::<T>()`, then
12801280
/// `self.deref_mut_unchecked()` may cause undefined behavior.
12811281
pub unsafe fn deref_mut_unchecked(&mut self) -> &mut T {
12821282
// SAFETY: `self.get_mut_ptr()` returns a raw pointer to a valid `T` at
@@ -1463,6 +1463,177 @@ macro_rules! transmute {
14631463
}}
14641464
}
14651465

1466+
/// A type whose size is equal to `align_of::<T>()`.
1467+
#[doc(hidden)]
1468+
#[allow(missing_debug_implementations)]
1469+
#[repr(C)]
1470+
pub struct AlignOf<T> {
1471+
// This field ensures that:
1472+
// - The size is always at least 1 (the minimum possible alignment).
1473+
// - If the alignment is greater than 1, Rust has to round up to the next
1474+
// multiple of it in order to make sure that `Align`'s size is a multiple
1475+
// of that alignment. Without this field, its size could be 0, which is a
1476+
// valid multiple of any alignment.
1477+
_u: u8,
1478+
_a: [T; 0],
1479+
}
1480+
1481+
impl<T> AlignOf<T> {
1482+
#[doc(hidden)]
1483+
pub fn into_t(self) -> T {
1484+
unreachable!()
1485+
}
1486+
}
1487+
1488+
/// A type whose size is equal to `max(align_of::<T>(), align_of::<U>())`.
1489+
#[doc(hidden)]
1490+
#[allow(missing_debug_implementations)]
1491+
#[repr(C)]
1492+
pub union MaxAlignsOf<T, U> {
1493+
_t: ManuallyDrop<AlignOf<T>>,
1494+
_u: ManuallyDrop<AlignOf<U>>,
1495+
}
1496+
1497+
impl<T, U> MaxAlignsOf<T, U> {
1498+
#[doc(hidden)]
1499+
pub fn new(_t: T, _u: U) -> MaxAlignsOf<T, U> {
1500+
unreachable!()
1501+
}
1502+
}
1503+
1504+
/// Safely transmutes a mutable or immutable reference of one type to an
1505+
/// immutable reference of another type of the same size.
1506+
///
1507+
/// The expression `$e` must have a concrete type, `&T` or `&mut T`, where `T:
1508+
/// Sized + AsBytes`. The `transmute_ref!` expression must also have a concrete
1509+
/// type, `&U` (`U` is inferred from the calling context), where `U: Sized +
1510+
/// FromBytes`. It must be the case that `align_of::<T>() >= align_of::<U>()`.
1511+
///
1512+
/// The lifetime of the input type, `&T` or `&mut T`, must be the same as or
1513+
/// outlive the lifetime of the output type, `&U`.
1514+
///
1515+
/// # Alignment increase error message
1516+
///
1517+
/// Because of limitations on macros, the error message generated when
1518+
/// `transmute_ref!` is used to transmute from a type of lower alignment to a
1519+
/// type of higher alignment is somewhat confusing. For example, the following
1520+
/// code:
1521+
///
1522+
/// ```rust,compile_fail
1523+
/// const INCREASE_ALIGNMENT: &u16 = zerocopy::transmute_ref!(&[0u8; 2]);
1524+
/// ```
1525+
///
1526+
/// ...generates the following error:
1527+
///
1528+
/// ```text
1529+
/// error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
1530+
/// --> src/lib.rs:1524:34
1531+
/// |
1532+
/// 5 | const INCREASE_ALIGNMENT: &u16 = zerocopy::transmute_ref!(&[0u8; 2]);
1533+
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1534+
/// |
1535+
/// = note: source type: `MaxAlignsOf<[u8; 2], u16>` (16 bits)
1536+
/// = note: target type: `AlignOf<[u8; 2]>` (8 bits)
1537+
/// = note: this error originates in the macro `zerocopy::transmute_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
1538+
/// ```
1539+
///
1540+
/// This is saying that `max(align_of::<T>(), align_of::<U>()) !=
1541+
/// align_of::<T>()`, which is equivalent to `align_of::<T>() <
1542+
/// align_of::<U>()`.
1543+
#[macro_export]
1544+
macro_rules! transmute_ref {
1545+
($e:expr) => {{
1546+
// NOTE: This must be a macro (rather than a function with trait bounds)
1547+
// because there's no way, in a generic context, to enforce that two
1548+
// types have the same size or alignment.
1549+
1550+
// Reborrow so that mutable references are supported too.
1551+
//
1552+
// In the rest of the comments, we refer only to `&T` since this
1553+
// reborrow ensures that `e` is an immutable reference.
1554+
let e = &*$e;
1555+
1556+
#[allow(unused, clippy::diverging_sub_expression)]
1557+
if false {
1558+
// This branch, though never taken, ensures that the type of `e` is
1559+
// `&T` where `T: 't + Sized + AsBytes`, that the type of this macro
1560+
// expression is `&U` where `U: 'u + Sized + FromBytes`, and that
1561+
// `'t` outlives `'u`.
1562+
const fn transmute<'u, 't: 'u, T: 't + Sized + $crate::AsBytes, U: 'u + Sized + $crate::FromBytes>(_t: &'t T) -> &'u U {
1563+
unreachable!()
1564+
}
1565+
transmute(e)
1566+
} else if false {
1567+
// This branch, though never taken, ensures that `size_of::<T>() ==
1568+
// size_of::<U>()`.
1569+
1570+
// `t` is inferred to have type `T` because it's assigned to `e` (of
1571+
// type `&T`) as `&t`.
1572+
let mut t = unreachable!();
1573+
e = &t;
1574+
1575+
// `u` is inferred to have type `U` because it's used as `&u` as the
1576+
// value returned from this branch.
1577+
//
1578+
// SAFETY: This code is never run.
1579+
let u = unsafe { $crate::__real_transmute(t) };
1580+
&u
1581+
} else if false {
1582+
// This branch, though never taken, ensures that the alignment of
1583+
// `T` is greater than or equal to to the alignment of `U`.
1584+
1585+
// `t` is inferred to have type `T` because it's assigned to `e` (of
1586+
// type `&T`) as `&t`.
1587+
let mut t = unreachable!();
1588+
e = &t;
1589+
1590+
// `u` is inferred to have type `U` because it's used as `&u` as the
1591+
// value returned from this branch.
1592+
let mut u = unreachable!();
1593+
1594+
// `max_aligns` is inferred to have type `MaxAlignsOf<T, U>` because
1595+
// of the inferred types of `t` and `u`.
1596+
let max_aligns = $crate::MaxAlignsOf::new(t, u);
1597+
// The type wildcard in this bound is inferred to be `T` because
1598+
// `align_of.into_t()` is assigned to `t` (which has type `T`).
1599+
//
1600+
// This transmute will only compile successfully if
1601+
// `max(align_of::<T>(), align_of::<U>()) == align_of::<T>()` - in
1602+
// other words, if `align_of::<T>() >= align_of::<U>()`.
1603+
//
1604+
// SAFETY: This code is never run.
1605+
let align_of: $crate::AlignOf<_> = unsafe { $crate::__real_transmute(max_aligns) };
1606+
t = align_of.into_t();
1607+
1608+
&u
1609+
} else {
1610+
// SAFETY:
1611+
// - We know that the input and output types are both `Sized` (ie,
1612+
// thin) references thanks to the trait bounds on `transmute`
1613+
// above, and thanks to the fact that transmute takes and returns
1614+
// references.
1615+
// - We know that it is sound to view the target type of the input
1616+
// reference (`T`) as the target type of the output reference
1617+
// (`U`) because `T: AsBytes` and `U: FromBytes` (guaranteed by
1618+
// trait bounds on `transmute`) and because `size_of::<T>() ==
1619+
// size_of::<U>()` (guaranteed by the first `__real_transmute`
1620+
// above).
1621+
// - We know that alignment is not increased thanks to the second
1622+
// `__real_transmute` above (the one which transmutes
1623+
// `MaxAlignsOf` into `AlignOf`).
1624+
//
1625+
// We use `$crate::__real_transmute` because we know it will always
1626+
// be available for crates which are using the 2015 edition of Rust.
1627+
// By contrast, if we were to use `std::mem::transmute`, this macro
1628+
// would not work for such crates in `no_std` contexts, and if we
1629+
// were to use `core::mem::transmute`, this macro would not work in
1630+
// `std` contexts in which `core` was not manually imported. This is
1631+
// not a problem for 2018 edition crates.
1632+
unsafe { $crate::__real_transmute(e) }
1633+
}
1634+
}}
1635+
}
1636+
14661637
/// A length- and alignment-checked reference to a byte slice which can safely
14671638
/// be reinterpreted as another type.
14681639
///
@@ -2320,11 +2491,11 @@ impl<T: ?Sized> AsAddress for *mut T {
23202491
}
23212492
}
23222493

2323-
/// Is `t` aligned to `mem::align_of::<U>()`?
2494+
/// Is `t` aligned to `align_of::<U>()`?
23242495
#[inline(always)]
23252496
fn aligned_to<T: AsAddress, U>(t: T) -> bool {
2326-
// `mem::align_of::<U>()` is guaranteed to return a non-zero value, which in
2327-
// turn guarantees that this mod operation will not panic.
2497+
// `align_of::<U>()` is guaranteed to return a non-zero value, which in turn
2498+
// guarantees that this mod operation will not panic.
23282499
#[allow(clippy::arithmetic_side_effects)]
23292500
let remainder = t.addr() % mem::align_of::<U>();
23302501
remainder == 0
@@ -3195,6 +3366,90 @@ mod tests {
31953366
assert_eq!(X, ARRAY_OF_ARRAYS);
31963367
}
31973368

3369+
#[test]
3370+
fn test_align_of() {
3371+
macro_rules! test {
3372+
($ty:ty) => {
3373+
assert_eq!(mem::size_of::<AlignOf<$ty>>(), mem::align_of::<$ty>());
3374+
};
3375+
}
3376+
3377+
test!(());
3378+
test!(u8);
3379+
test!(AU64);
3380+
test!([AU64; 2]);
3381+
}
3382+
3383+
#[test]
3384+
fn test_max_aligns_of() {
3385+
macro_rules! test {
3386+
($t:ty, $u:ty) => {
3387+
assert_eq!(
3388+
mem::size_of::<MaxAlignsOf<$t, $u>>(),
3389+
core::cmp::max(mem::align_of::<$t>(), mem::align_of::<$u>())
3390+
);
3391+
};
3392+
}
3393+
3394+
test!(u8, u8);
3395+
test!(u8, AU64);
3396+
test!(AU64, u8);
3397+
}
3398+
3399+
#[test]
3400+
fn test_typed_align_check() {
3401+
// Test that the type-based alignment check used in `transmute_ref!`
3402+
// behaves as expected.
3403+
3404+
macro_rules! assert_t_align_gteq_u_align {
3405+
($t:ty, $u:ty, $gteq:expr) => {
3406+
assert_eq!(
3407+
mem::size_of::<MaxAlignsOf<$t, $u>>() == mem::size_of::<AlignOf<$t>>(),
3408+
$gteq
3409+
);
3410+
};
3411+
}
3412+
3413+
assert_t_align_gteq_u_align!(u8, u8, true);
3414+
assert_t_align_gteq_u_align!(AU64, AU64, true);
3415+
assert_t_align_gteq_u_align!(AU64, u8, true);
3416+
assert_t_align_gteq_u_align!(u8, AU64, false);
3417+
}
3418+
3419+
#[test]
3420+
fn test_transmute_ref() {
3421+
// Test that memory is transmuted as expected.
3422+
let array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7];
3423+
let array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]];
3424+
let x: &[[u8; 2]; 4] = transmute_ref!(&array_of_u8s);
3425+
assert_eq!(*x, array_of_arrays);
3426+
let x: &[u8; 8] = transmute_ref!(&array_of_arrays);
3427+
assert_eq!(*x, array_of_u8s);
3428+
3429+
// Test that `transmute_ref!` is legal in a const context.
3430+
const ARRAY_OF_U8S: [u8; 8] = [0u8, 1, 2, 3, 4, 5, 6, 7];
3431+
const ARRAY_OF_ARRAYS: [[u8; 2]; 4] = [[0, 1], [2, 3], [4, 5], [6, 7]];
3432+
#[allow(clippy::redundant_static_lifetimes)]
3433+
const X: &'static [[u8; 2]; 4] = transmute_ref!(&ARRAY_OF_U8S);
3434+
assert_eq!(*X, ARRAY_OF_ARRAYS);
3435+
3436+
// Test that it's legal to transmute a reference while shrinking the
3437+
// lifetime (note that `X` has the lifetime `'static`).
3438+
let x: &[u8; 8] = transmute_ref!(X);
3439+
assert_eq!(*x, ARRAY_OF_U8S);
3440+
3441+
// Test that `transmute_ref!` supports decreasing alignment.
3442+
let u = AU64(0);
3443+
let array = [0, 0, 0, 0, 0, 0, 0, 0];
3444+
let x: &[u8; 8] = transmute_ref!(&u);
3445+
assert_eq!(*x, array);
3446+
3447+
// Test that a mutable reference can be turned into an immutable one.
3448+
let mut x = 0u8;
3449+
let y: &u8 = transmute_ref!(&mut x);
3450+
assert_eq!(*y, 0);
3451+
}
3452+
31983453
#[test]
31993454
fn test_address() {
32003455
// Test that the `Deref` and `DerefMut` implementations return a
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-illegal-lifetime.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
error[E0597]: `x` does not live long enough
2+
--> tests/ui-msrv/transmute-illegal-lifetime.rs:12:52
3+
|
4+
12 | let _: &'static u64 = zerocopy::transmute_ref!(&x);
5+
| ------------ ^^ borrowed value does not live long enough
6+
| |
7+
| type annotation requires that `x` is borrowed for `'static`
8+
13 | }
9+
| - `x` dropped here while still borrowed

0 commit comments

Comments
 (0)