Skip to content

Commit 59e8401

Browse files
committed
Add try_transmute!, try_transmute_{ref,mut}!
TODO: Body Makes progress on #5
1 parent d7c16fa commit 59e8401

File tree

1 file changed

+189
-2
lines changed

1 file changed

+189
-2
lines changed

src/lib.rs

Lines changed: 189 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1939,14 +1939,16 @@ mod simd {
19391939
simd_arch_mod!(arm, int8x4_t, uint8x4_t);
19401940
}
19411941

1942-
// Used in `transmute!` below.
1942+
// Used in macros below.
19431943
#[doc(hidden)]
19441944
pub use core::mem::transmute as __real_transmute;
1945+
#[doc(hidden)]
1946+
pub use core::mem::ManuallyDrop as __RealManuallyDrop;
19451947

19461948
/// Safely transmutes a value of one type to a value of another type of the same
19471949
/// size.
19481950
///
1949-
/// The expression `$e` must have a concrete type, `T`, which implements
1951+
/// The expression, `$e`, must have a concrete type, `T`, which implements
19501952
/// `AsBytes`. The `transmute!` expression must also have a concrete type, `U`
19511953
/// (`U` is inferred from the calling context), and `U` must implement
19521954
/// `FromBytes`.
@@ -1989,6 +1991,160 @@ macro_rules! transmute {
19891991
}}
19901992
}
19911993

1994+
/// Safely attempts to transmute a value of one type to a value of another type
1995+
/// of the same size, failing if the transmute would be unsound.
1996+
///
1997+
/// The expression, `$e`, must have a concrete type, `T`, which implements
1998+
/// `AsBytes`. The `try_transmute!` expression must also have a concrete type,
1999+
/// `Option<U>` (`U` is inferred from the calling context), and `U` must
2000+
/// implement `TryFromBytes`.
2001+
///
2002+
/// [`TryFromBytes::try_read_from`] is used to attempt to convert `$e` to the
2003+
/// output type `U`. This will fail if the bytes of `$e` do not correspond to a
2004+
/// valid instance of `U`.
2005+
///
2006+
/// Note that the `T` produced by the expression `$e` will *not* be dropped.
2007+
/// Semantically, its bits will be copied into a new value of type `U`, the
2008+
/// original `T` will be forgotten, and the value of type `U` will be returned.
2009+
///
2010+
/// # Examples
2011+
///
2012+
/// ```rust
2013+
/// # use zerocopy::try_transmute;
2014+
/// assert_eq!(try_transmute!(1u8), Some(true));
2015+
/// assert_eq!(try_transmute!(2u8), None::<bool>);
2016+
///
2017+
/// assert_eq!(try_transmute!(108u32), Some('l'));
2018+
/// assert_eq!(try_transmute!(0xD800u32), None::<char>);
2019+
/// ```
2020+
#[macro_export]
2021+
macro_rules! try_transmute {
2022+
($e:expr) => {{
2023+
// NOTE: This must be a macro (rather than a function with trait bounds)
2024+
// because there's no way, in a generic context, to enforce that two
2025+
// types have the same size. `core::mem::transmute` uses compiler magic
2026+
// to enforce this so long as the types are concrete.
2027+
2028+
let e = $e;
2029+
if false {
2030+
// This branch, though never taken, ensures that the type of `e` is
2031+
// `AsBytes` and that the type of this macro invocation expression
2032+
// is `TryFromBytes`.
2033+
const fn transmute<T: $crate::AsBytes, U: $crate::TryFromBytes>(_t: T) -> Option<U> {
2034+
unreachable!()
2035+
}
2036+
transmute(e)
2037+
} else {
2038+
// TODO: What's the correct drop behavior on `None`? Does this just
2039+
// behave like `mem::forget` in that case?
2040+
let m = $crate::__RealManuallyDrop::new(e);
2041+
$crate::TryFromBytes::try_read_from($crate::AsBytes::as_bytes(&m))
2042+
}
2043+
}}
2044+
}
2045+
2046+
/// Safely attempts to transmute a reference of one type to a reference of
2047+
/// another type, failing if the transmute would be unsound.
2048+
///
2049+
/// The expression, `$e`, must have a concrete type, `&T`, where `T: AsBytes`.
2050+
/// The `try_transmute_ref!` expression must also have a concrete type,
2051+
/// `Option<&U>` (`U` is inferred from the calling context), and `U` must
2052+
/// implement `TryFromBytes`.
2053+
///
2054+
/// [`TryFromBytes::try_from_ref`] is used to attempt to convert `$e` to the
2055+
/// output reference type `&U`. This will fail if `$e` is not the right size, is
2056+
/// not properly aligned, or if the bytes of `$e` do not correspond to a valid
2057+
/// instance of `U`.
2058+
///
2059+
/// Note that, if `U` is an unsized type, there will be multiple sizes for `$e`
2060+
/// which correspond to valid values of `U`.
2061+
///
2062+
/// # Examples
2063+
///
2064+
/// ```rust
2065+
/// # use zerocopy::try_transmute_ref;
2066+
/// # use zerocopy::AsBytes as _;
2067+
/// let s: Option<&str> = try_transmute_ref!(&[104u8, 101, 108, 108, 111]);
2068+
/// assert_eq!(s, Some("hello"));
2069+
///
2070+
/// // Invalid UTF-8
2071+
/// assert_eq!(try_transmute_ref!(&0xFFFFFFFFu32), None::<&str>);
2072+
///
2073+
/// // Not enough bytes for a `u8`
2074+
/// assert_eq!(try_transmute_ref!(&()), None::<&u8>);
2075+
///
2076+
/// // Valid `&[[u8; 2]]` slices could be 2 or 4 bytes long,
2077+
/// // but not 3.
2078+
/// assert_eq!(try_transmute_ref!(&[0u8, 1, 2]), None::<&[[u8; 2]]>);
2079+
///
2080+
/// // Guaranteed to be invalidly-aligned so long as
2081+
/// // `align_of::<u16>() == 2` and `align_of::<u32>() >= 2`
2082+
/// // (this is true on most targets, but it isn't guaranteed).
2083+
/// assert_eq!(try_transmute_ref!(&0u32.as_bytes()[1..]), None::<&u16>);
2084+
/// ```
2085+
#[macro_export]
2086+
macro_rules! try_transmute_ref {
2087+
($e:expr) => {
2088+
$crate::TryFromBytes::try_from_ref($crate::AsBytes::as_bytes($e))
2089+
};
2090+
}
2091+
2092+
/// Safely attempts to transmute a mutable reference of one type to a mutable
2093+
/// reference of another type, failing if the transmute would be unsound.
2094+
///
2095+
/// The expression, `$e`, must have a concrete type, `&mut T`, where `T:
2096+
/// FromBytes + AsBytes`. The `try_transmute_ref!` expression must also have a
2097+
/// concrete type, `Option<&mut U>` (`U` is inferred from the calling context),
2098+
/// and `U` must implement `TryFromBytes`.
2099+
///
2100+
/// [`TryFromBytes::try_from_mut`] is used to attempt to convert `$e` to the
2101+
/// output reference type, `&mut U`. This will fail if `$e` is not the right
2102+
/// size, is not properly aligned, or if the bytes of `$e` do not correspond to
2103+
/// a valid instance of `U`.
2104+
///
2105+
/// Note that, if `U` is an unsized type, there will be multiple sizes for `$e`
2106+
/// which correspond to valid values of `U`.
2107+
///
2108+
/// # Examples
2109+
///
2110+
/// ```rust
2111+
/// # use zerocopy::try_transmute_mut;
2112+
/// # use zerocopy::AsBytes as _;
2113+
/// let bytes = &mut [104u8, 101, 108, 108, 111];
2114+
/// let mut s = try_transmute_mut!(bytes);
2115+
/// assert_eq!(s, Some(String::from("hello").as_mut_str()));
2116+
///
2117+
/// // Mutations to the transmuted reference are reflected
2118+
/// // in the original reference.
2119+
/// s.as_mut().unwrap().make_ascii_uppercase();
2120+
/// assert_eq!(bytes, &[72, 69, 76, 76, 79]);
2121+
///
2122+
/// // Invalid UTF-8
2123+
/// let mut u = 0xFFFFFFFFu32;
2124+
/// assert_eq!(try_transmute_mut!(&mut u), None::<&mut str>);
2125+
///
2126+
/// // Not enough bytes for a `u8`
2127+
/// let mut tuple = ();
2128+
/// assert_eq!(try_transmute_mut!(&mut tuple), None::<&mut u8>);
2129+
///
2130+
/// // Valid `&mut [[u8; 2]]` slices could be 2 or 4 bytes
2131+
/// // long, but not 3.
2132+
/// let bytes = &mut [0u8, 1, 2];
2133+
/// assert_eq!(try_transmute_mut!(bytes), None::<&mut [[u8; 2]]>);
2134+
///
2135+
/// // Guaranteed to be invalidly-aligned so long as
2136+
/// // `align_of::<u16>() == 2` and `align_of::<u32>() >= 2`
2137+
/// // (this is true on most targets, but it isn't guaranteed).
2138+
/// let mut u = 0u32;
2139+
/// assert_eq!(try_transmute_mut!(&mut u.as_bytes_mut()[1..]), None::<&mut u16>);
2140+
/// ```
2141+
#[macro_export]
2142+
macro_rules! try_transmute_mut {
2143+
($e:expr) => {
2144+
$crate::TryFromBytes::try_from_mut($crate::AsBytes::as_bytes_mut($e))
2145+
};
2146+
}
2147+
19922148
/// A typed reference derived from a byte slice.
19932149
///
19942150
/// A `Ref<B, T>` is a reference to a `T` which is stored in a byte slice, `B`.
@@ -3599,10 +3755,16 @@ mod tests {
35993755
// Test that memory is transmuted as expected.
36003756
let array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7];
36013757
let array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]];
3758+
36023759
let x: [[u8; 2]; 4] = transmute!(array_of_u8s);
36033760
assert_eq!(x, array_of_arrays);
3761+
let x: Option<[[u8; 2]; 4]> = try_transmute!(array_of_u8s);
3762+
assert_eq!(x, Some(array_of_arrays));
3763+
36043764
let x: [u8; 8] = transmute!(array_of_arrays);
36053765
assert_eq!(x, array_of_u8s);
3766+
let x: Option<[u8; 8]> = try_transmute!(array_of_arrays);
3767+
assert_eq!(x, Some(array_of_u8s));
36063768

36073769
// Test that the source expression's value is forgotten rather than
36083770
// dropped.
@@ -3615,12 +3777,37 @@ mod tests {
36153777
}
36163778
}
36173779
let _: () = transmute!(PanicOnDrop(()));
3780+
let _: Option<()> = try_transmute!(PanicOnDrop(()));
36183781

36193782
// Test that `transmute!` is legal in a const context.
36203783
const ARRAY_OF_U8S: [u8; 8] = [0u8, 1, 2, 3, 4, 5, 6, 7];
36213784
const ARRAY_OF_ARRAYS: [[u8; 2]; 4] = [[0, 1], [2, 3], [4, 5], [6, 7]];
36223785
const X: [[u8; 2]; 4] = transmute!(ARRAY_OF_U8S);
36233786
assert_eq!(X, ARRAY_OF_ARRAYS);
3787+
3788+
// Test fallible transmutations with `try_transmute!`.
3789+
let mut b: Option<bool> = try_transmute!(0u8);
3790+
assert_eq!(b, Some(false));
3791+
b = try_transmute!(1u8);
3792+
assert_eq!(b, Some(true));
3793+
b = try_transmute!(2u8);
3794+
assert_eq!(b, None);
3795+
}
3796+
3797+
#[test]
3798+
fn test_try_transmute_ref_mut() {
3799+
// These macros are dead-simple thin wrappers which delegate to other
3800+
// traits. We only have this test to ensure that the macros are uesd
3801+
// somewhere so our tests will break if the paths to various items
3802+
// break.
3803+
let x: Option<&[u8; 2]> = try_transmute_ref!(&0xFFFFu16);
3804+
assert_eq!(x, Some(&[255, 255]));
3805+
3806+
let mut u = 0xFFFFu16;
3807+
let x: Option<&mut [u8; 2]> = try_transmute_mut!(&mut u);
3808+
assert_eq!(x, Some(&mut [255, 255]));
3809+
*x.unwrap() = [0, 0];
3810+
assert_eq!(u, 0);
36243811
}
36253812

36263813
#[test]

0 commit comments

Comments
 (0)