Skip to content

Commit 31fd332

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 27aea67 commit 31fd332

9 files changed

+1025
-16
lines changed

Diff for: src/lib.rs

+127
Original file line numberDiff line numberDiff line change
@@ -1463,6 +1463,96 @@ macro_rules! transmute {
14631463
}}
14641464
}
14651465

1466+
/// A type whose size is equal to `mem::align_of::<T>()`.
1467+
///
1468+
/// This type is used internally in `transmute_ref!`, and so needs to be
1469+
/// exported. It is not intended for direct use by users of zerocopy, and so is
1470+
/// `#[doc(hidden)]`.
1471+
#[doc(hidden)]
1472+
#[allow(missing_debug_implementations)]
1473+
#[repr(C)]
1474+
pub struct SizeIsAlign<T> {
1475+
// This field ensures that:
1476+
// - The size is always at least 1 (the minimum possible alignment).
1477+
// - If the alignment is greater than 1, Rust has to round up to the next
1478+
// multiple of it in order to make sure that `SizeIsAlign`'s size is a
1479+
// multiple of that alignment. Without this field, its size could be 0,
1480+
// which is a valid multiple of any alignment.
1481+
_u: u8,
1482+
_a: [T; 0],
1483+
}
1484+
1485+
impl<T> SizeIsAlign<T> {
1486+
#[doc(hidden)]
1487+
pub fn new(_t: T) -> SizeIsAlign<T> {
1488+
SizeIsAlign { _u: 0, _a: [] }
1489+
}
1490+
1491+
#[doc(hidden)]
1492+
pub fn into_t(self) -> T {
1493+
unreachable!()
1494+
}
1495+
}
1496+
1497+
/// Safely transmutes an immutable reference of one type to an immutable
1498+
/// reference of another type of the same size and alignment.
1499+
///
1500+
/// The expression `$e` must have a concrete type, `&T`, where `T: Sized +
1501+
/// AsBytes`. The `transmute_ref!` expression must also have a concrete type,
1502+
/// `&U` (`U` is inferred from the calling context), where `U: Sized +
1503+
/// FromBytes`.
1504+
///
1505+
/// The lifetime of the input type, `&T`, must be the same as or outlive the
1506+
/// lifetime of the output type, `&U`.
1507+
#[macro_export]
1508+
macro_rules! transmute_ref {
1509+
($e:expr) => {{
1510+
// NOTE: This must be a macro (rather than a function with trait bounds)
1511+
// because there's no way, in a generic context, to enforce that two
1512+
// types have the same size or alignment. `core::mem::transmute` uses
1513+
// compiler magic to enforce size equality so long as the types are
1514+
// concrete. We use `SizeIsAlign` to create a type whose size is equal
1515+
// to the alignment of another type so that we can use `transmute` to
1516+
// check alignment as well.
1517+
1518+
let e = $e;
1519+
#[allow(unused, clippy::diverging_sub_expression)]
1520+
if false {
1521+
// This branch, though never taken, ensures that the type of `e` is
1522+
// `&T` where `T: 't + Sized + AsBytes`, that the type of this macro
1523+
// expression is `&U` where `U: 'u + Sized + FromBytes`, and that
1524+
// `'t` outlives `'u`.
1525+
const fn transmute<'u, 't: 'u, T: 't + Sized + $crate::AsBytes, U: 'u + Sized + $crate::FromBytes>(_t: &'t T) -> &'u U {
1526+
unreachable!()
1527+
}
1528+
transmute(e)
1529+
} else if false {
1530+
// This branch, though never taken, ensures that the alignment of
1531+
// `T` is equal to the alignment of `U`.
1532+
let target = unreachable!();
1533+
e = &target;
1534+
1535+
// SAFETY: This code is never executed.
1536+
let ret: $crate::SizeIsAlign<_> = unsafe { $crate::__real_transmute($crate::SizeIsAlign::new(target)) };
1537+
&ret.into_t()
1538+
} else {
1539+
// SAFETY: `core::mem::transmute` ensures that the type of `e` and
1540+
// the type of this macro invocation expression have the same size.
1541+
// We know this transmute is safe thanks to the `AsBytes` and
1542+
// `FromBytes` bounds enforced by the `false` branch.
1543+
//
1544+
// We use `$crate::__real_transmute` because we know it will always
1545+
// be available for crates which are using the 2015 edition of Rust.
1546+
// By contrast, if we were to use `std::mem::transmute`, this macro
1547+
// would not work for such crates in `no_std` contexts, and if we
1548+
// were to use `core::mem::transmute`, this macro would not work in
1549+
// `std` contexts in which `core` was not manually imported. This is
1550+
// not a problem for 2018 edition crates.
1551+
unsafe { $crate::__real_transmute(e) }
1552+
}
1553+
}}
1554+
}
1555+
14661556
/// A length- and alignment-checked reference to a byte slice which can safely
14671557
/// be reinterpreted as another type.
14681558
///
@@ -3195,6 +3285,43 @@ mod tests {
31953285
assert_eq!(X, ARRAY_OF_ARRAYS);
31963286
}
31973287

3288+
#[test]
3289+
fn test_size_is_align() {
3290+
macro_rules! test {
3291+
($ty:ty) => {
3292+
assert_eq!(mem::size_of::<SizeIsAlign<$ty>>(), mem::align_of::<$ty>());
3293+
};
3294+
}
3295+
3296+
test!(());
3297+
test!(u8);
3298+
test!(AU64);
3299+
test!([AU64; 2]);
3300+
}
3301+
3302+
#[test]
3303+
fn test_transmute_ref() {
3304+
// Test that memory is transmuted as expected.
3305+
let array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7];
3306+
let array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]];
3307+
let x: &[[u8; 2]; 4] = transmute_ref!(&array_of_u8s);
3308+
assert_eq!(*x, array_of_arrays);
3309+
let x: &[u8; 8] = transmute_ref!(&array_of_arrays);
3310+
assert_eq!(*x, array_of_u8s);
3311+
3312+
// Test that `transmute_ref!` is legal in a const context.
3313+
const ARRAY_OF_U8S: [u8; 8] = [0u8, 1, 2, 3, 4, 5, 6, 7];
3314+
const ARRAY_OF_ARRAYS: [[u8; 2]; 4] = [[0, 1], [2, 3], [4, 5], [6, 7]];
3315+
#[allow(clippy::redundant_static_lifetimes)]
3316+
const X: &'static [[u8; 2]; 4] = transmute_ref!(&ARRAY_OF_U8S);
3317+
assert_eq!(*X, ARRAY_OF_ARRAYS);
3318+
3319+
// Test that it's legal to transmute a reference while shrinking the
3320+
// lifetime (note that `X` has the lifetime `'static`).
3321+
let x: &[u8; 8] = transmute_ref!(X);
3322+
assert_eq!(*x, ARRAY_OF_U8S);
3323+
}
3324+
31983325
#[test]
31993326
fn test_address() {
32003327
// Test that the `Deref` and `DerefMut` implementations return a

Diff for: tests/ui-msrv/transmute-illegal-lifetime.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui/transmute-illegal-lifetime.rs

Diff for: tests/ui-msrv/transmute-illegal-lifetime.stderr

+9
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)