Skip to content

Commit 718acaa

Browse files
committed
Implement try_transmute!
Closes #1013. Makes progress towards #5.
1 parent dac2194 commit 718acaa

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

src/lib.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,6 +1521,45 @@ pub unsafe trait TryFromBytes {
15211521
#[doc(hidden)]
15221522
fn is_bit_valid<A: invariant::at_least::Shared>(candidate: Maybe<'_, Self, A>) -> bool;
15231523

1524+
/// Is a given source value a valid instance of `Self`?
1525+
///
1526+
/// # Safety
1527+
///
1528+
/// Unsafe code may assume that, if `is_bit_valid(src)` returns true, `*src`
1529+
/// is valid instance of `Self`.
1530+
///
1531+
/// # Panics
1532+
///
1533+
/// `is_src_valid` panics under exactly the same circumstances as
1534+
/// [`TryFromBytes::is_bit_valid`].
1535+
#[doc(hidden)]
1536+
#[inline]
1537+
fn is_src_valid<Src>(src: &mut Src) -> bool
1538+
where
1539+
Src: IntoBytes,
1540+
Self: Sized,
1541+
{
1542+
let c_ptr = crate::Ptr::from_mut(src);
1543+
1544+
if mem::size_of::<Src>() > mem::size_of::<Self>() {
1545+
return false;
1546+
}
1547+
1548+
// SAFETY:
1549+
// - `size_of::<Src>()` <= `size_of::<Self>()`
1550+
// - `c_ptr` is exclusively aliased, so we do not need to reason about
1551+
// `UnsafeCell`
1552+
#[allow(clippy::as_conversions)]
1553+
let c_ptr = unsafe { c_ptr.cast_unsized(|p| p as *mut Self) };
1554+
1555+
// SAFETY: `c_ptr` is derived from `src` which is `IntoBytes`. By
1556+
// invariant on `IntoByte`s, `c_ptr`'s referent consists entirely of
1557+
// initialized bytes.
1558+
let c_ptr = unsafe { c_ptr.assume_validity::<crate::invariant::Initialized>() };
1559+
1560+
Self::is_bit_valid(c_ptr)
1561+
}
1562+
15241563
/// Attempts to interpret a byte slice as a `Self`.
15251564
///
15261565
/// `try_from_ref` validates that `bytes` contains a valid `Self`, and that
@@ -4732,6 +4771,63 @@ macro_rules! transmute_mut {
47324771
}}
47334772
}
47344773

4774+
/// Conditionally transmutes a value of one type to a value of another type of
4775+
/// the same size.
4776+
///
4777+
/// The expression `$e` must have a concrete type, `T`, which implements
4778+
/// [`IntoBytes`]. The `try_transmute!` expression must also have a concrete
4779+
/// type, `U` (`U` is inferred from the calling context), and `U` must implement
4780+
/// [`TryFromBytes`].
4781+
///
4782+
/// Note that the `T` produced by the expression `$e` will *not* be dropped.
4783+
/// Semantically, its bits will be copied into a new value of type `U`, the
4784+
/// original `T` will be forgotten, and the value of type `U` will be returned.
4785+
///
4786+
/// # Examples
4787+
///
4788+
/// ```
4789+
/// # use zerocopy::try_transmute;
4790+
/// assert_eq!(try_transmute!(0u8), Some(false));
4791+
/// assert_eq!(try_transmute!(1u8), Some(true));
4792+
/// assert_eq!(try_transmute!(255u8), Option::<bool>::None);
4793+
/// ```
4794+
#[macro_export]
4795+
macro_rules! try_transmute {
4796+
($e:expr) => {{
4797+
// NOTE: This must be a macro (rather than a function with trait bounds)
4798+
// because there's no way, in a generic context, to enforce that two
4799+
// types have the same size. `core::mem::transmute` uses compiler magic
4800+
// to enforce this so long as the types are concrete.
4801+
4802+
let e = $e;
4803+
if false {
4804+
// This branch, though never taken, ensures that the type of `e` is
4805+
// `IntoBytes` and that the type of this macro invocation expression
4806+
// is `TryFromBytes`.
4807+
4808+
struct AssertIsIntoBytes<T: $crate::IntoBytes>(T);
4809+
let _ = AssertIsIntoBytes(e);
4810+
4811+
struct AssertIsTryFromBytes<U: $crate::TryFromBytes>(U);
4812+
#[allow(unused, unreachable_code)]
4813+
let u = AssertIsTryFromBytes(loop {});
4814+
Some(u.0)
4815+
} else if false {
4816+
// Check that the sizes of the source and destination types are
4817+
// equal.
4818+
4819+
// SAFETY: This code is never executed.
4820+
Some(unsafe {
4821+
// Clippy: It's okay to transmute a type to itself.
4822+
#[allow(clippy::useless_transmute)]
4823+
$crate::macro_util::core_reexport::mem::transmute(e)
4824+
})
4825+
} else {
4826+
$crate::macro_util::try_transmute::<_, _>(e)
4827+
}
4828+
}}
4829+
}
4830+
47354831
/// Includes a file and safely transmutes it to a value of an arbitrary type.
47364832
///
47374833
/// The file will be included as a byte array, `[u8; N]`, which will be

src/macro_util.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,25 @@ pub unsafe fn transmute_mut<'dst, 'src: 'dst, Src: 'src, Dst: 'dst>(
404404
unsafe { &mut *dst }
405405
}
406406

407+
/// A helper for `try_transmute!`.
408+
#[inline(always)]
409+
pub fn try_transmute<Src, Dst>(mut src: Src) -> Option<Dst>
410+
where
411+
Src: core::fmt::Debug + crate::IntoBytes,
412+
Dst: core::fmt::Debug + crate::TryFromBytes,
413+
{
414+
if !Dst::is_src_valid(&mut src) {
415+
return None;
416+
}
417+
418+
let src = ManuallyDrop::new(src);
419+
420+
// SAFETY:
421+
// - `src` is a bit-valid instance of `Dst`
422+
// - `src` is `ManuallyDrop`
423+
Some(unsafe { core::mem::transmute_copy(&*src) })
424+
}
425+
407426
// NOTE: We can't change this to a `pub use core as core_reexport` until [1] is
408427
// fixed or we update to a semver-breaking version (as of this writing, 0.8.0)
409428
// on the `main` branch.

0 commit comments

Comments
 (0)