Skip to content

Commit f0d420a

Browse files
committed
Implement try_transmute_{ref,mut}
Makes progress towards #5.
1 parent ea35de2 commit f0d420a

File tree

74 files changed

+1596
-42
lines changed

Some content is hidden

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

74 files changed

+1596
-42
lines changed

README.md

+6-4
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,12 @@ Zerocopy provides four macros for safe, zero-cost casting between types:
6060

6161
- (`try_`[try_transmute])`transmute` (conditionally) converts a value of
6262
one type to a value of another type of the same size
63-
- `transmute_mut` converts a mutable reference of one type to a mutable
64-
reference of another type of the same size
65-
- `transmute_ref` converts a mutable or immutable reference
66-
of one type to an immutable reference of another type of the same size
63+
- (`try_`[try_transmute_mut])`transmute_mut` (conditionally) converts a
64+
mutable reference of one type to a mutable reference of another type of
65+
the same size
66+
- (`try_`[try_transmute_ref])`transmute_ref` (conditionally) converts a
67+
mutable or immutable reference of one type to an immutable reference of
68+
another type of the same size
6769

6870
These macros perform *compile-time* alignment and size checks, but cannot be
6971
used in generic contexts. For generic conversions, use the methods defined

src/lib.rs

+298-7
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,12 @@
6060
//!
6161
//! - ([`try_`][try_transmute])[`transmute`] (conditionally) converts a value of
6262
//! one type to a value of another type of the same size
63-
//! - [`transmute_mut`] converts a mutable reference of one type to a mutable
64-
//! reference of another type of the same size
65-
//! - [`transmute_ref`] converts a mutable or immutable reference
66-
//! of one type to an immutable reference of another type of the same size
63+
//! - ([`try_`][try_transmute_mut])[`transmute_mut`] (conditionally) converts a
64+
//! mutable reference of one type to a mutable reference of another type of
65+
//! the same size
66+
//! - ([`try_`][try_transmute_ref])[`transmute_ref`] (conditionally) converts a
67+
//! mutable or immutable reference of one type to an immutable reference of
68+
//! another type of the same size
6769
//!
6870
//! These macros perform *compile-time* alignment and size checks, but cannot be
6971
//! used in generic contexts. For generic conversions, use the methods defined
@@ -4750,7 +4752,7 @@ macro_rules! transmute_mut {
47504752
/// This macro behaves like an invocation of this function:
47514753
///
47524754
/// ```ignore
4753-
/// const fn try_transmute<Src, Dst>(src: Src) -> Result<Dst, ValidityError<Src, Dst>>
4755+
/// fn try_transmute<Src, Dst>(src: Src) -> Result<Dst, ValidityError<Src, Dst>>
47544756
/// where
47554757
/// Src: IntoBytes,
47564758
/// Dst: TryFromBytes,
@@ -4784,10 +4786,9 @@ macro_rules! transmute_mut {
47844786
///
47854787
/// // 2u8 → bool = error
47864788
/// assert!(matches!(
4787-
/// try_transmute!(3u8),
4789+
/// try_transmute!(2u8),
47884790
/// Result::<bool, _>::Err(ValidityError { .. })
47894791
/// ));
4790-
///
47914792
/// ```
47924793
#[macro_export]
47934794
macro_rules! try_transmute {
@@ -4814,6 +4815,231 @@ macro_rules! try_transmute {
48144815
}}
48154816
}
48164817

4818+
/// Conditionally transmutes a mutable or immutable reference of one type to an
4819+
/// immutable reference of another type of the same size.
4820+
///
4821+
/// This macro behaves like an invocation of this function:
4822+
///
4823+
/// ```ignore
4824+
/// fn try_transmute_ref<Src, Dst>(src: &Src) -> Result<&Dst, ValidityError<&Src, &Dst>>
4825+
/// where
4826+
/// Src: IntoBytes + Immutable,
4827+
/// Dst: TryFromBytes + Immutable,
4828+
/// size_of::<Src>() == size_of::<Dst>(),
4829+
/// align_of::<Src>() >= align_of::<Dst>(),
4830+
/// {
4831+
/// # /*
4832+
/// ...
4833+
/// # */
4834+
/// }
4835+
/// ```
4836+
///
4837+
/// However, unlike a function, this macro can only be invoked when the types of
4838+
/// `Src` and `Dst` are completely concrete. The types `Src` and `Dst` are
4839+
/// inferred from the calling context; they cannot be explicitly specified in
4840+
/// the macro invocation.
4841+
///
4842+
/// Note that the `Src` produced by the expression `$e` will *not* be dropped.
4843+
/// Semantically, its bits will be copied into a new value of type `Dst`, the
4844+
/// original `Src` will be forgotten, and the value of type `Dst` will be
4845+
/// returned.
4846+
///
4847+
/// # Examples
4848+
///
4849+
/// ```
4850+
/// # use zerocopy::*;
4851+
/// // 0u8 → bool = false
4852+
/// assert_eq!(try_transmute_ref!(&0u8), Ok(&false));
4853+
///
4854+
/// // 1u8 → bool = true
4855+
/// assert_eq!(try_transmute_ref!(&1u8), Ok(&true));
4856+
///
4857+
/// // 2u8 → bool = error
4858+
/// assert!(matches!(
4859+
/// try_transmute_ref!(&2u8),
4860+
/// Result::<&bool, _>::Err(ValidityError { .. })
4861+
/// ));
4862+
/// ```
4863+
///
4864+
/// # Alignment increase error message
4865+
///
4866+
/// Because of limitations on macros, the error message generated when
4867+
/// `try_transmute_ref!` is used to transmute from a type of lower alignment to
4868+
/// a type of higher alignment is somewhat confusing. For example, the following
4869+
/// code:
4870+
///
4871+
/// ```compile_fail
4872+
/// let increase_alignment: Result<&u16, _> = zerocopy::try_transmute_ref!(&[0u8; 2]);
4873+
/// ```
4874+
///
4875+
/// ...generates the following error:
4876+
///
4877+
/// ```text
4878+
/// error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
4879+
/// --> example.rs:1:47
4880+
/// |
4881+
/// 1 | let increase_alignment: Result<&u16, _> = zerocopy::try_transmute_ref!(&[0u8; 2]);
4882+
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4883+
/// |
4884+
/// = note: source type: `AlignOf<[u8; 2]>` (8 bits)
4885+
/// = note: target type: `MaxAlignsOf<[u8; 2], u16>` (16 bits)
4886+
/// = note: this error originates in the macro `$crate::assert_align_gt_eq` which comes from the expansion of the macro `zerocopy::try_transmute_ref` (in Nightly builds, run with -Z macro-backtrace for more info)/// ```
4887+
/// ```
4888+
///
4889+
/// This is saying that `max(align_of::<T>(), align_of::<U>()) !=
4890+
/// align_of::<T>()`, which is equivalent to `align_of::<T>() <
4891+
/// align_of::<U>()`.
4892+
#[macro_export]
4893+
macro_rules! try_transmute_ref {
4894+
($e:expr) => {{
4895+
// NOTE: This must be a macro (rather than a function with trait bounds)
4896+
// because there's no way, in a generic context, to enforce that two
4897+
// types have the same size. `core::mem::transmute` uses compiler magic
4898+
// to enforce this so long as the types are concrete.
4899+
4900+
// Ensure that the source type is a reference or a mutable reference
4901+
// (note that mutable references are implicitly reborrowed here).
4902+
let e: &_ = $e;
4903+
4904+
#[allow(unreachable_code, unused, clippy::diverging_sub_expression)]
4905+
if false {
4906+
// This branch, though never taken, ensures that `size_of::<T>() ==
4907+
// size_of::<U>()` and that that `align_of::<T>() >=
4908+
// align_of::<U>()`.
4909+
4910+
// `t` is inferred to have type `T` because it's assigned to `e` (of
4911+
// type `&T`) as `&t`.
4912+
let mut t = loop {};
4913+
e = &t;
4914+
4915+
// `u` is inferred to have type `U` because it's used as `&u` as the
4916+
// value returned from this branch.
4917+
let u;
4918+
4919+
$crate::assert_size_eq!(t, u);
4920+
$crate::assert_align_gt_eq!(t, u);
4921+
4922+
Ok(&u)
4923+
} else {
4924+
$crate::macro_util::try_transmute_ref::<_, _>(e)
4925+
}
4926+
}}
4927+
}
4928+
4929+
/// Conditionally transmutes a mutable reference of one type to a mutable
4930+
/// reference of another type of the same size.
4931+
///
4932+
/// This macro behaves like an invocation of this function:
4933+
///
4934+
/// ```ignore
4935+
/// fn try_transmute_mut<Src, Dst>(src: &mut Src) -> Result<&mut Dst, ValidityError<&mut Src, &mut Dst>>
4936+
/// where
4937+
/// Src: IntoBytes,
4938+
/// Dst: TryFromBytes,
4939+
/// size_of::<Src>() == size_of::<Dst>(),
4940+
/// align_of::<Src>() >= align_of::<Dst>(),
4941+
/// {
4942+
/// # /*
4943+
/// ...
4944+
/// # */
4945+
/// }
4946+
/// ```
4947+
///
4948+
/// However, unlike a function, this macro can only be invoked when the types of
4949+
/// `Src` and `Dst` are completely concrete. The types `Src` and `Dst` are
4950+
/// inferred from the calling context; they cannot be explicitly specified in
4951+
/// the macro invocation.
4952+
///
4953+
/// Note that the `Src` produced by the expression `$e` will *not* be dropped.
4954+
/// Semantically, its bits will be copied into a new value of type `Dst`, the
4955+
/// original `Src` will be forgotten, and the value of type `Dst` will be
4956+
/// returned.
4957+
///
4958+
/// # Examples
4959+
///
4960+
/// ```
4961+
/// # use zerocopy::*;
4962+
/// // 0u8 → bool = false
4963+
/// let src = &mut 0u8;
4964+
/// assert_eq!(try_transmute_mut!(src), Ok(&mut false));
4965+
///
4966+
/// // 1u8 → bool = true
4967+
/// let src = &mut 1u8;
4968+
/// assert_eq!(try_transmute_mut!(src), Ok(&mut true));
4969+
///
4970+
/// // 2u8 → bool = error
4971+
/// let src = &mut 2u8;
4972+
/// assert!(matches!(
4973+
/// try_transmute_mut!(src),
4974+
/// Result::<&mut bool, _>::Err(ValidityError { .. })
4975+
/// ));
4976+
/// ```
4977+
///
4978+
/// # Alignment increase error message
4979+
///
4980+
/// Because of limitations on macros, the error message generated when
4981+
/// `try_transmute_ref!` is used to transmute from a type of lower alignment to
4982+
/// a type of higher alignment is somewhat confusing. For example, the following
4983+
/// code:
4984+
///
4985+
/// ```compile_fail
4986+
/// let src = &mut [0u8; 2];
4987+
/// let increase_alignment: Result<&mut u16, _> = zerocopy::try_transmute_mut!(src);
4988+
/// ```
4989+
///
4990+
/// ...generates the following error:
4991+
///
4992+
/// ```text
4993+
/// error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
4994+
/// --> example.rs:2:51
4995+
/// |
4996+
/// 2 | let increase_alignment: Result<&mut u16, _> = zerocopy::try_transmute_mut!(src);
4997+
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4998+
/// |
4999+
/// = note: source type: `AlignOf<[u8; 2]>` (8 bits)
5000+
/// = note: target type: `MaxAlignsOf<[u8; 2], u16>` (16 bits)
5001+
/// = note: this error originates in the macro `$crate::assert_align_gt_eq` which comes from the expansion of the macro `zerocopy::try_transmute_mut` (in Nightly builds, run with -Z macro-backtrace for more info)
5002+
/// ```
5003+
///
5004+
/// This is saying that `max(align_of::<T>(), align_of::<U>()) !=
5005+
/// align_of::<T>()`, which is equivalent to `align_of::<T>() <
5006+
/// align_of::<U>()`.
5007+
#[macro_export]
5008+
macro_rules! try_transmute_mut {
5009+
($e:expr) => {{
5010+
// NOTE: This must be a macro (rather than a function with trait bounds)
5011+
// because there's no way, in a generic context, to enforce that two
5012+
// types have the same size. `core::mem::transmute` uses compiler magic
5013+
// to enforce this so long as the types are concrete.
5014+
5015+
// Ensure that the source type is a mutable reference.
5016+
let e: &mut _ = $e;
5017+
5018+
#[allow(unreachable_code, unused, clippy::diverging_sub_expression)]
5019+
if false {
5020+
// This branch, though never taken, ensures that `size_of::<T>() ==
5021+
// size_of::<U>()` and that that `align_of::<T>() >=
5022+
// align_of::<U>()`.
5023+
5024+
// `t` is inferred to have type `T` because it's assigned to `e` (of
5025+
// type `&mut T`) as `&mut t`.
5026+
let mut t = loop {};
5027+
e = &mut t;
5028+
5029+
// `u` is inferred to have type `U` because it's used as `&mut u` as
5030+
// the value returned from this branch.
5031+
let u;
5032+
5033+
$crate::assert_size_eq!(t, u);
5034+
$crate::assert_align_gt_eq!(t, u);
5035+
5036+
Ok(&mut u)
5037+
} else {
5038+
$crate::macro_util::try_transmute_mut::<_, _>(e)
5039+
}
5040+
}}
5041+
}
5042+
48175043
/// Includes a file and safely transmutes it to a value of an arbitrary type.
48185044
///
48195045
/// The file will be included as a byte array, `[u8; N]`, which will be
@@ -5775,6 +6001,71 @@ mod tests {
57756001
mem::forget(y);
57766002
}
57776003

6004+
#[test]
6005+
fn test_try_transmute_ref() {
6006+
// Test that memory is transmuted with `try_transmute_ref` as expected.
6007+
let array_of_bools = &[false, true, false, true, false, true, false, true];
6008+
let array_of_arrays = &[[0, 1], [0, 1], [0, 1], [0, 1]];
6009+
let x: Result<&[[u8; 2]; 4], _> = try_transmute_ref!(array_of_bools);
6010+
assert_eq!(x, Ok(array_of_arrays));
6011+
let x: Result<&[bool; 8], _> = try_transmute_ref!(array_of_arrays);
6012+
assert_eq!(x, Ok(array_of_bools));
6013+
6014+
// Test that it's legal to transmute a reference while shrinking the
6015+
// lifetime.
6016+
{
6017+
let x: Result<&[[u8; 2]; 4], _> = try_transmute_ref!(array_of_bools);
6018+
assert_eq!(x, Ok(array_of_arrays));
6019+
}
6020+
6021+
// Test that `try_transmute_ref!` supports decreasing alignment.
6022+
let u = AU64(0);
6023+
let array = [0u8, 0, 0, 0, 0, 0, 0, 0];
6024+
let x: Result<&[u8; 8], _> = try_transmute_ref!(&u);
6025+
assert_eq!(x, Ok(&array));
6026+
6027+
// Test that a mutable reference can be turned into an immutable one.
6028+
let mut x = 0u8;
6029+
#[allow(clippy::useless_transmute)]
6030+
let y: Result<&u8, _> = try_transmute_ref!(&mut x);
6031+
assert_eq!(y, Ok(&0));
6032+
}
6033+
6034+
#[test]
6035+
fn test_try_transmute_mut() {
6036+
// Test that memory is transmuted with `try_transmute_mut` as expected.
6037+
let array_of_bools = &mut [false, true, false, true, false, true, false, true];
6038+
let array_of_arrays = &mut [[0u8, 1], [0, 1], [0, 1], [0, 1]];
6039+
let x: Result<&mut [[u8; 2]; 4], _> = try_transmute_mut!(array_of_bools);
6040+
assert_eq!(x, Ok(array_of_arrays));
6041+
6042+
let array_of_bools = &mut [false, true, false, true, false, true, false, true];
6043+
let array_of_arrays = &mut [[0u8, 1], [0, 1], [0, 1], [0, 1]];
6044+
let x: Result<&mut [bool; 8], _> = try_transmute_mut!(array_of_arrays);
6045+
assert_eq!(x, Ok(array_of_bools));
6046+
6047+
// Test that it's legal to transmute a reference while shrinking the
6048+
// lifetime.
6049+
let array_of_bools = &mut [false, true, false, true, false, true, false, true];
6050+
let array_of_arrays = &mut [[0u8, 1], [0, 1], [0, 1], [0, 1]];
6051+
{
6052+
let x: Result<&mut [[u8; 2]; 4], _> = try_transmute_mut!(array_of_bools);
6053+
assert_eq!(x, Ok(array_of_arrays));
6054+
}
6055+
6056+
// Test that `try_transmute_mut!` supports decreasing alignment.
6057+
let u = &mut AU64(0);
6058+
let array = &mut [0u8, 0, 0, 0, 0, 0, 0, 0];
6059+
let x: Result<&mut [u8; 8], _> = try_transmute_mut!(u);
6060+
assert_eq!(x, Ok(array));
6061+
6062+
// Test that a mutable reference can be turned into an immutable one.
6063+
let mut x = 0u8;
6064+
#[allow(clippy::useless_transmute)]
6065+
let y: Result<&mut u8, _> = try_transmute_mut!(&mut x);
6066+
assert_eq!(y, Ok(&mut 0));
6067+
}
6068+
57786069
#[test]
57796070
fn test_transmute_mut() {
57806071
// Test that memory is transmuted as expected.

0 commit comments

Comments
 (0)