Skip to content

Commit 12e6b88

Browse files
committed
Add transmute_ref! macro
This macro is like the existing `transmute!`, but it transmutes immutable references rather than values. Release 0.7.8. Issue #159
1 parent edffeac commit 12e6b88

File tree

102 files changed

+2556
-13
lines changed

Some content is hidden

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

102 files changed

+2556
-13
lines changed

Diff for: .github/workflows/ci.yml

+7-3
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,17 @@ jobs:
122122
with:
123123
toolchain: ${{ env.ZC_TOOLCHAIN }}
124124
targets: ${{ matrix.target }}
125+
# We require the `rust-src` component to ensure that the compiler
126+
# error output generated during UI tests matches that generated on
127+
# local developer machines; see
128+
# https://github.com/rust-lang/rust/issues/116433.
129+
#
125130
# Only nightly has a working Miri, so we skip installing on all other
126131
# toolchains. This expression is effectively a ternary expression -
127132
# see [1] for details.
128133
#
129-
# [1]
130-
# https://github.com/actions/runner/issues/409#issuecomment-752775072
131-
components: clippy ${{ matrix.toolchain == 'nightly' && ', miri' || '' }}
134+
# [1] https://github.com/actions/runner/issues/409#issuecomment-752775072
135+
components: clippy, rust-src ${{ matrix.toolchain == 'nightly' && ', miri' || '' }}
132136

133137
- name: Rust Cache
134138
uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0

Diff for: Cargo.toml

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
[package]
1212
edition = "2021"
1313
name = "zerocopy"
14-
version = "0.7.7"
14+
version = "0.7.8"
1515
authors = ["Joshua Liebow-Feeser <[email protected]>"]
1616
description = "Utilities for zero-copy parsing and serialization"
1717
license = "BSD-2-Clause"
@@ -41,7 +41,7 @@ simd-nightly = ["simd"]
4141
__internal_use_only_features_that_work_on_stable = ["alloc", "derive", "simd"]
4242

4343
[dependencies]
44-
zerocopy-derive = { version = "=0.7.7", path = "zerocopy-derive", optional = true }
44+
zerocopy-derive = { version = "=0.7.8", path = "zerocopy-derive", optional = true }
4545

4646
[dependencies.byteorder]
4747
version = "1.3"
@@ -52,7 +52,7 @@ optional = true
5252
# zerocopy-derive remain equal, even if the 'derive' feature isn't used.
5353
# See: https://github.com/matklad/macro-dep-test
5454
[target.'cfg(any())'.dependencies]
55-
zerocopy-derive = { version = "=0.7.7", path = "zerocopy-derive" }
55+
zerocopy-derive = { version = "=0.7.8", path = "zerocopy-derive" }
5656

5757
[dev-dependencies]
5858
assert_matches = "1.5"
@@ -67,4 +67,4 @@ testutil = { path = "testutil" }
6767
# CI test failures.
6868
trybuild = { version = "=1.0.85", features = ["diff"] }
6969
# In tests, unlike in production, zerocopy-derive is not optional
70-
zerocopy-derive = { version = "=0.7.7", path = "zerocopy-derive" }
70+
zerocopy-derive = { version = "=0.7.8", path = "zerocopy-derive" }

Diff for: src/lib.rs

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

3960+
#[test]
3961+
fn test_transmute_ref() {
3962+
// Test that memory is transmuted as expected.
3963+
let array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7];
3964+
let array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]];
3965+
let x: &[[u8; 2]; 4] = transmute_ref!(&array_of_u8s);
3966+
assert_eq!(*x, array_of_arrays);
3967+
let x: &[u8; 8] = transmute_ref!(&array_of_arrays);
3968+
assert_eq!(*x, array_of_u8s);
3969+
3970+
// Test that `transmute_ref!` is legal in a const context.
3971+
const ARRAY_OF_U8S: [u8; 8] = [0u8, 1, 2, 3, 4, 5, 6, 7];
3972+
const ARRAY_OF_ARRAYS: [[u8; 2]; 4] = [[0, 1], [2, 3], [4, 5], [6, 7]];
3973+
#[allow(clippy::redundant_static_lifetimes)]
3974+
const X: &'static [[u8; 2]; 4] = transmute_ref!(&ARRAY_OF_U8S);
3975+
assert_eq!(*X, ARRAY_OF_ARRAYS);
3976+
3977+
// Test that it's legal to transmute a reference while shrinking the
3978+
// lifetime (note that `X` has the lifetime `'static`).
3979+
let x: &[u8; 8] = transmute_ref!(X);
3980+
assert_eq!(*x, ARRAY_OF_U8S);
3981+
3982+
// Test that `transmute_ref!` supports decreasing alignment.
3983+
let u = AU64(0);
3984+
let array = [0, 0, 0, 0, 0, 0, 0, 0];
3985+
let x: &[u8; 8] = transmute_ref!(&u);
3986+
assert_eq!(*x, array);
3987+
3988+
// Test that a mutable reference can be turned into an immutable one.
3989+
let mut x = 0u8;
3990+
#[allow(clippy::useless_transmute)]
3991+
let y: &u8 = transmute_ref!(&mut x);
3992+
assert_eq!(*y, 0);
3993+
}
3994+
3995+
#[test]
3996+
fn test_macros_evaluate_args_once() {
3997+
let mut ctr = 0;
3998+
let _: usize = transmute!({
3999+
ctr += 1;
4000+
0usize
4001+
});
4002+
assert_eq!(ctr, 1);
4003+
4004+
let mut ctr = 0;
4005+
let _: &usize = transmute_ref!({
4006+
ctr += 1;
4007+
&0usize
4008+
});
4009+
assert_eq!(ctr, 1);
4010+
}
4011+
38134012
#[test]
38144013
fn test_address() {
38154014
// Test that the `Deref` and `DerefMut` implementations return a

Diff for: src/macro_util.rs

+88-1
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
@@ -71,8 +105,61 @@ pub mod core_reexport {
71105

72106
#[cfg(test)]
73107
mod tests {
108+
use core::mem;
109+
110+
use super::*;
74111
use crate::util::testutil::*;
75112

113+
#[test]
114+
fn test_align_of() {
115+
macro_rules! test {
116+
($ty:ty) => {
117+
assert_eq!(mem::size_of::<AlignOf<$ty>>(), mem::align_of::<$ty>());
118+
};
119+
}
120+
121+
test!(());
122+
test!(u8);
123+
test!(AU64);
124+
test!([AU64; 2]);
125+
}
126+
127+
#[test]
128+
fn test_max_aligns_of() {
129+
macro_rules! test {
130+
($t:ty, $u:ty) => {
131+
assert_eq!(
132+
mem::size_of::<MaxAlignsOf<$t, $u>>(),
133+
core::cmp::max(mem::align_of::<$t>(), mem::align_of::<$u>())
134+
);
135+
};
136+
}
137+
138+
test!(u8, u8);
139+
test!(u8, AU64);
140+
test!(AU64, u8);
141+
}
142+
143+
#[test]
144+
fn test_typed_align_check() {
145+
// Test that the type-based alignment check used in `transmute_ref!`
146+
// behaves as expected.
147+
148+
macro_rules! assert_t_align_gteq_u_align {
149+
($t:ty, $u:ty, $gteq:expr) => {
150+
assert_eq!(
151+
mem::size_of::<MaxAlignsOf<$t, $u>>() == mem::size_of::<AlignOf<$t>>(),
152+
$gteq
153+
);
154+
};
155+
}
156+
157+
assert_t_align_gteq_u_align!(u8, u8, true);
158+
assert_t_align_gteq_u_align!(AU64, AU64, true);
159+
assert_t_align_gteq_u_align!(AU64, u8, true);
160+
assert_t_align_gteq_u_align!(u8, AU64, false);
161+
}
162+
76163
#[test]
77164
fn test_struct_has_padding() {
78165
// Test that, for each provided repr, `struct_has_padding!` reports the

Diff for: tests/ui-msrv/transmute-ref-alignment-increase.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-ref-alignment-increase.rs

0 commit comments

Comments
 (0)