Skip to content

Commit 1a0b1bb

Browse files
committed
Add try_transmute!, try_transmute_{ref,mut}!
TODO: Commit message body TODO: - In `try_transmute!`, should the argument be dropped or forgotten (ie, `mem::forget`) when the transmute fails? We could also return the original value in a `Result::Error`, but that would be inconsistent with `TryFrom`. Then again, `TryFrom` provides a custom error type, so in theory implementers could do that if they wanted to. Most of the types that don't consume Copy types. Makes progress on #5
1 parent 05e5775 commit 1a0b1bb

File tree

3 files changed

+275
-4
lines changed

3 files changed

+275
-4
lines changed

src/lib.rs

Lines changed: 229 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1987,21 +1987,43 @@ mod simd {
19871987
simd_arch_mod!(arm, int8x4_t, uint8x4_t);
19881988
}
19891989

1990-
// Used in `transmute!` below.
1990+
// Used in macros below.
19911991
#[doc(hidden)]
19921992
pub use core::mem::transmute as __real_transmute;
1993+
#[doc(hidden)]
1994+
pub use core::mem::ManuallyDrop as __RealManuallyDrop;
19931995

19941996
/// Safely transmutes a value of one type to a value of another type of the same
19951997
/// size.
19961998
///
1997-
/// The expression `$e` must have a concrete type, `T`, which implements
1998-
/// `AsBytes`. The `transmute!` expression must also have a concrete type, `U`
1999+
/// The expression, `$e`, must have a concrete type, `T`, which implements
2000+
/// [`AsBytes`]. The `transmute!` expression must also have a concrete type, `U`
19992001
/// (`U` is inferred from the calling context), and `U` must implement
2000-
/// `FromBytes`.
2002+
/// [`FromBytes`]. `T` and `U` must have the same size.
20012003
///
20022004
/// Note that the `T` produced by the expression `$e` will *not* be dropped.
20032005
/// Semantically, its bits will be copied into a new value of type `U`, the
20042006
/// original `T` will be forgotten, and the value of type `U` will be returned.
2007+
///
2008+
/// # Examples
2009+
///
2010+
/// ```rust
2011+
/// # use zerocopy::transmute;
2012+
/// use core::num::NonZeroU64;
2013+
///
2014+
/// // Why would you want to do this? Who knows ¯\_(ツ)_/¯
2015+
/// let opt: Option<NonZeroU64> = transmute!(0.0f64);
2016+
/// assert_eq!(opt, None);
2017+
/// ```
2018+
///
2019+
/// ```rust,compile_fail
2020+
/// # use zerocopy::try_transmute;
2021+
/// // Fails to compile: `bool` does not implement `FromBytes`
2022+
/// assert_eq!(transmute!(1u8), true);
2023+
///
2024+
/// // Fails to compile: can't transmute between sizes of different types
2025+
/// let _: u8 = try_transmute!(0u16);
2026+
/// ```
20052027
#[macro_export]
20062028
macro_rules! transmute {
20072029
($e:expr) => {{
@@ -2037,6 +2059,178 @@ macro_rules! transmute {
20372059
}}
20382060
}
20392061

2062+
/// Safely attempts to transmute a value of one type to a value of another type
2063+
/// of the same size, failing if the transmute would be unsound.
2064+
///
2065+
/// The expression, `$e`, must have a concrete type, `T`, which implements
2066+
/// [`AsBytes`]. The `try_transmute!` expression must also have a concrete type,
2067+
/// `Option<U>` (`U` is inferred from the calling context), and `U` must
2068+
/// implement [`TryFromBytes`]. `T` and `U` must have the same size.
2069+
///
2070+
/// [`TryFromBytes::try_read_from`] is used to attempt to convert `$e` to the
2071+
/// output type `U`. This will fail if the bytes of `$e` do not correspond to a
2072+
/// valid instance of `U`.
2073+
///
2074+
/// Note that the `T` produced by the expression `$e` will *not* be dropped.
2075+
/// Semantically, its bits will be copied into a new value of type `U`, the
2076+
/// original `T` will be forgotten, and the value of type `U` will be returned.
2077+
///
2078+
/// # Examples
2079+
///
2080+
/// ```rust
2081+
/// # use zerocopy::try_transmute;
2082+
/// assert_eq!(try_transmute!(1u8), Some(true));
2083+
/// assert_eq!(try_transmute!(2u8), None::<bool>);
2084+
///
2085+
/// assert_eq!(try_transmute!(108u32), Some('l'));
2086+
/// assert_eq!(try_transmute!(0xD800u32), None::<char>);
2087+
/// ```
2088+
///
2089+
/// ```rust,compile_fail
2090+
/// # use zerocopy::try_transmute;
2091+
/// // Attempting to transmute from 2 to 1 bytes will fail to compile
2092+
/// let _: Option<u8> = try_transmute!(0u16);
2093+
/// ```
2094+
#[macro_export]
2095+
macro_rules! try_transmute {
2096+
($e:expr) => {{
2097+
// NOTE: This must be a macro (rather than a function with trait bounds)
2098+
// because there's no way, in a generic context, to enforce that two
2099+
// types have the same size. `core::mem::transmute` uses compiler magic
2100+
// to enforce this so long as the types are concrete.
2101+
2102+
let e = $e;
2103+
if false {
2104+
// This branch, though never taken, ensures that the type of `e` is
2105+
// `AsBytes` and that the type of this macro invocation expression
2106+
// is `TryFromBytes`.
2107+
const fn transmute<T: $crate::AsBytes, U: $crate::TryFromBytes>(_t: T) -> U {
2108+
unreachable!()
2109+
}
2110+
Some(transmute(e))
2111+
} else if false {
2112+
// Though never executed, this ensures that the source and
2113+
// destination types have the same size. This isn't strictly
2114+
// necessary for soundness, but it turns what would otherwise be
2115+
// runtime errors into compile-time errors.
2116+
//
2117+
// SAFETY: This branch never executes.
2118+
Some(unsafe { $crate::__real_transmute(e) })
2119+
} else {
2120+
// TODO: What's the correct drop behavior on `None`? Does this just
2121+
// behave like `mem::forget` in that case?
2122+
let m = $crate::__RealManuallyDrop::new(e);
2123+
$crate::TryFromBytes::try_read_from($crate::AsBytes::as_bytes(&m))
2124+
}
2125+
}}
2126+
}
2127+
2128+
/// Safely attempts to transmute a reference of one type to a reference of
2129+
/// another type, failing if the transmute would be unsound.
2130+
///
2131+
/// The expression, `$e`, must have a concrete type, `&T`, where [`T: AsBytes`].
2132+
/// The `try_transmute_ref!` expression must also have a concrete type,
2133+
/// `Option<&U>` (`U` is inferred from the calling context), and `U` must
2134+
/// implement [`TryFromBytes`].
2135+
///
2136+
/// [`TryFromBytes::try_from_ref`] is used to attempt to convert `$e` to the
2137+
/// output reference type `&U`. This will fail if `$e` is not the right size, is
2138+
/// not properly aligned, or if the bytes of `$e` do not correspond to a valid
2139+
/// instance of `U`.
2140+
///
2141+
/// Note that, if `U` is an unsized type, there will be multiple sizes for `$e`
2142+
/// which correspond to valid values of `U`.
2143+
///
2144+
/// [`T: AsBytes`]: AsBytes
2145+
///
2146+
/// # Examples
2147+
///
2148+
/// ```rust
2149+
/// # use zerocopy::try_transmute_ref;
2150+
/// # use zerocopy::AsBytes as _;
2151+
/// let s: Option<&str> = try_transmute_ref!(&[104u8, 101, 108, 108, 111]);
2152+
/// assert_eq!(s, Some("hello"));
2153+
///
2154+
/// // Invalid UTF-8
2155+
/// assert_eq!(try_transmute_ref!(&0xFFFFFFFFu32), None::<&str>);
2156+
///
2157+
/// // Not enough bytes for a `u8`
2158+
/// assert_eq!(try_transmute_ref!(&()), None::<&u8>);
2159+
///
2160+
/// // Valid `&[[u8; 2]]` slices could be 2 or 4 bytes long,
2161+
/// // but not 3.
2162+
/// assert_eq!(try_transmute_ref!(&[0u8, 1, 2]), None::<&[[u8; 2]]>);
2163+
///
2164+
/// // Guaranteed to be invalidly-aligned so long as
2165+
/// // `align_of::<u16>() == 2` and `align_of::<u32>() >= 2`
2166+
/// // (this is true on most targets, but it isn't guaranteed).
2167+
/// assert_eq!(try_transmute_ref!(&0u32.as_bytes()[1..]), None::<&u16>);
2168+
/// ```
2169+
#[macro_export]
2170+
macro_rules! try_transmute_ref {
2171+
($e:expr) => {
2172+
$crate::TryFromBytes::try_from_ref($crate::AsBytes::as_bytes($e))
2173+
};
2174+
}
2175+
2176+
/// Safely attempts to transmute a mutable reference of one type to a mutable
2177+
/// reference of another type, failing if the transmute would be unsound.
2178+
///
2179+
/// The expression, `$e`, must have a concrete type, `&mut T`, where `T:
2180+
/// FromBytes + AsBytes`. The `try_transmute_ref!` expression must also have a
2181+
/// concrete type, `Option<&mut U>` (`U` is inferred from the calling context),
2182+
/// and `U` must implement [`TryFromBytes`].
2183+
///
2184+
/// [`TryFromBytes::try_from_mut`] is used to attempt to convert `$e` to the
2185+
/// output reference type, `&mut U`. This will fail if `$e` is not the right
2186+
/// size, is not properly aligned, or if the bytes of `$e` do not correspond to
2187+
/// a valid instance of `U`.
2188+
///
2189+
/// Note that, if `U` is an unsized type, there will be multiple sizes for `$e`
2190+
/// which correspond to valid values of `U`.
2191+
///
2192+
/// [`TryFromBytes`]: TryFromBytes
2193+
///
2194+
/// # Examples
2195+
///
2196+
/// ```rust
2197+
/// # use zerocopy::try_transmute_mut;
2198+
/// # use zerocopy::AsBytes as _;
2199+
/// let bytes = &mut [104u8, 101, 108, 108, 111];
2200+
/// let mut s = try_transmute_mut!(bytes);
2201+
/// assert_eq!(s, Some(String::from("hello").as_mut_str()));
2202+
///
2203+
/// // Mutations to the transmuted reference are reflected
2204+
/// // in the original reference.
2205+
/// s.as_mut().unwrap().make_ascii_uppercase();
2206+
/// assert_eq!(bytes, &[72, 69, 76, 76, 79]);
2207+
///
2208+
/// // Invalid UTF-8
2209+
/// let mut u = 0xFFFFFFFFu32;
2210+
/// assert_eq!(try_transmute_mut!(&mut u), None::<&mut str>);
2211+
///
2212+
/// // Not enough bytes for a `u8`
2213+
/// let mut tuple = ();
2214+
/// assert_eq!(try_transmute_mut!(&mut tuple), None::<&mut u8>);
2215+
///
2216+
/// // Valid `&mut [[u8; 2]]` slices could be 2 or 4 bytes
2217+
/// // long, but not 3.
2218+
/// let bytes = &mut [0u8, 1, 2];
2219+
/// assert_eq!(try_transmute_mut!(bytes), None::<&mut [[u8; 2]]>);
2220+
///
2221+
/// // Guaranteed to be invalidly-aligned so long as
2222+
/// // `align_of::<u16>() == 2` and `align_of::<u32>() >= 2`
2223+
/// // (this is true on most targets, but it isn't guaranteed).
2224+
/// let mut u = 0u32;
2225+
/// assert_eq!(try_transmute_mut!(&mut u.as_bytes_mut()[1..]), None::<&mut u16>);
2226+
/// ```
2227+
#[macro_export]
2228+
macro_rules! try_transmute_mut {
2229+
($e:expr) => {
2230+
$crate::TryFromBytes::try_from_mut($crate::AsBytes::as_bytes_mut($e))
2231+
};
2232+
}
2233+
20402234
/// A typed reference derived from a byte slice.
20412235
///
20422236
/// A `Ref<B, T>` is a reference to a `T` which is stored in a byte slice, `B`.
@@ -3718,10 +3912,16 @@ mod tests {
37183912
// Test that memory is transmuted as expected.
37193913
let array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7];
37203914
let array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]];
3915+
37213916
let x: [[u8; 2]; 4] = transmute!(array_of_u8s);
37223917
assert_eq!(x, array_of_arrays);
3918+
let x: Option<[[u8; 2]; 4]> = try_transmute!(array_of_u8s);
3919+
assert_eq!(x, Some(array_of_arrays));
3920+
37233921
let x: [u8; 8] = transmute!(array_of_arrays);
37243922
assert_eq!(x, array_of_u8s);
3923+
let x: Option<[u8; 8]> = try_transmute!(array_of_arrays);
3924+
assert_eq!(x, Some(array_of_u8s));
37253925

37263926
// Test that the source expression's value is forgotten rather than
37273927
// dropped.
@@ -3734,12 +3934,37 @@ mod tests {
37343934
}
37353935
}
37363936
let _: () = transmute!(PanicOnDrop(()));
3937+
let _: Option<()> = try_transmute!(PanicOnDrop(()));
37373938

37383939
// Test that `transmute!` is legal in a const context.
37393940
const ARRAY_OF_U8S: [u8; 8] = [0u8, 1, 2, 3, 4, 5, 6, 7];
37403941
const ARRAY_OF_ARRAYS: [[u8; 2]; 4] = [[0, 1], [2, 3], [4, 5], [6, 7]];
37413942
const X: [[u8; 2]; 4] = transmute!(ARRAY_OF_U8S);
37423943
assert_eq!(X, ARRAY_OF_ARRAYS);
3944+
3945+
// Test fallible transmutations with `try_transmute!`.
3946+
let mut b: Option<bool> = try_transmute!(0u8);
3947+
assert_eq!(b, Some(false));
3948+
b = try_transmute!(1u8);
3949+
assert_eq!(b, Some(true));
3950+
b = try_transmute!(2u8);
3951+
assert_eq!(b, None);
3952+
}
3953+
3954+
#[test]
3955+
fn test_try_transmute_ref_mut() {
3956+
// These macros are dead-simple thin wrappers which delegate to other
3957+
// traits. We only have this test to ensure that the macros are uesd
3958+
// somewhere so our tests will break if the paths to various items
3959+
// break.
3960+
let x: Option<&[u8; 2]> = try_transmute_ref!(&0xFFFFu16);
3961+
assert_eq!(x, Some(&[255, 255]));
3962+
3963+
let mut u = 0xFFFFu16;
3964+
let x: Option<&mut [u8; 2]> = try_transmute_mut!(&mut u);
3965+
assert_eq!(x, Some(&mut [255, 255]));
3966+
*x.unwrap() = [0, 0];
3967+
assert_eq!(u, 0);
37433968
}
37443969

37453970
#[test]

tests/ui-nightly/transmute-illegal.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,9 @@ fn main() {}
88

99
// It is unsound to inspect the usize value of a pointer during const eval.
1010
const POINTER_VALUE: usize = zerocopy::transmute!(&0usize as *const usize);
11+
12+
// `transmute!` and `try_transmute!` enforce size equality.
13+
const TOO_LARGE: u64 = zerocopy::transmute!(0u8);
14+
const TRY_TOO_LARGE: Option<u64> = zerocopy::try_transmute!(0u8);
15+
const TOO_SMALL: u8 = zerocopy::transmute!(0u64);
16+
const TRY_TOO_SMALL: Option<u8> = zerocopy::try_transmute!(0u64);

tests/ui-nightly/transmute-illegal.stderr

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,43 @@ note: required by a bound in `POINTER_VALUE::transmute`
1414
10 | const POINTER_VALUE: usize = zerocopy::transmute!(&0usize as *const usize);
1515
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `transmute`
1616
= note: this error originates in the macro `zerocopy::transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
17+
18+
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
19+
--> tests/ui-nightly/transmute-illegal.rs:13:24
20+
|
21+
13 | const TOO_LARGE: u64 = zerocopy::transmute!(0u8);
22+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
23+
|
24+
= note: source type: `u8` (8 bits)
25+
= note: target type: `u64` (64 bits)
26+
= note: this error originates in the macro `zerocopy::transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
27+
28+
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
29+
--> tests/ui-nightly/transmute-illegal.rs:14:36
30+
|
31+
14 | const TRY_TOO_LARGE: Option<u64> = zerocopy::try_transmute!(0u8);
32+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
33+
|
34+
= note: source type: `u8` (8 bits)
35+
= note: target type: `u64` (64 bits)
36+
= note: this error originates in the macro `zerocopy::try_transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
37+
38+
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
39+
--> tests/ui-nightly/transmute-illegal.rs:15:23
40+
|
41+
15 | const TOO_SMALL: u8 = zerocopy::transmute!(0u64);
42+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
43+
|
44+
= note: source type: `u64` (64 bits)
45+
= note: target type: `u8` (8 bits)
46+
= note: this error originates in the macro `zerocopy::transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
47+
48+
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
49+
--> tests/ui-nightly/transmute-illegal.rs:16:35
50+
|
51+
16 | const TRY_TOO_SMALL: Option<u8> = zerocopy::try_transmute!(0u64);
52+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
53+
|
54+
= note: source type: `u64` (64 bits)
55+
= note: target type: `u8` (8 bits)
56+
= note: this error originates in the macro `zerocopy::try_transmute` (in Nightly builds, run with -Z macro-backtrace for more info)

0 commit comments

Comments
 (0)