Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace transmute! macro with transmute fn #190

Draft
wants to merge 1 commit into
base: more-transmute-ui-tests
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 47 additions & 53 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1413,54 +1413,48 @@ impl<T: Unaligned + Display> Display for Unalign<T> {
}
}

// Used in `transmute!` below.
#[doc(hidden)]
pub use core::mem::transmute as __real_transmute;
pub trait TransmuteFrom<Src>: Sized {
Copy link
Contributor

@kupiakos kupiakos Jul 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This trait does not need to be (and shouldn't be) pub, because it has a blanket implementation on T: Sized that transmute can use without declaring it in its where bounds.

const IS_TRANSMUTABLE: bool;
}

impl<Src, Dst> TransmuteFrom<Src> for Dst
where
Src: AsBytes,
Dst: FromBytes,
{
const IS_TRANSMUTABLE: bool = {
assert!(mem::size_of::<Src>() == mem::size_of::<Dst>());
true
};
}

/// Safely transmutes a value of one type to a value of another type of the same
/// size.
///
/// The expression `$e` must have a concrete type, `T`, which implements
/// `AsBytes`. The `transmute!` expression must also have a concrete type, `U`
/// (`U` is inferred from the calling context), and `U` must implement
/// `FromBytes`.
///
/// Note that the `T` produced by the expression `$e` will *not* be dropped.
/// Semantically, its bits will be copied into a new value of type `U`, the
/// original `T` will be forgotten, and the value of type `U` will be returned.
#[macro_export]
macro_rules! transmute {
($e:expr) => {{
// NOTE: This must be a macro (rather than a function with trait bounds)
// because there's no way, in a generic context, to enforce that two
// types have the same size. `core::mem::transmute` uses compiler magic
// to enforce this so long as the types are concrete.

let e = $e;
if false {
// This branch, though never taken, ensures that the type of `e` is
// `AsBytes` and that the type of this macro invocation expression
// is `FromBytes`.
const fn transmute<T: $crate::AsBytes, U: $crate::FromBytes>(_t: T) -> U {
unreachable!()
}
transmute(e)
} else {
// SAFETY: `core::mem::transmute` ensures that the type of `e` and
// the type of this macro invocation expression have the same size.
// We know this transmute is safe thanks to the `AsBytes` and
// `FromBytes` bounds enforced by the `false` branch.
//
// We use `$crate::__real_transmute` because we know it will always
// be available for crates which are using the 2015 edition of Rust.
// By contrast, if we were to use `std::mem::transmute`, this macro
// would not work for such crates in `no_std` contexts, and if we
// were to use `core::mem::transmute`, this macro would not work in
// `std` contexts in which `core` was not manually imported. This is
// not a problem for 2018 edition crates.
unsafe { $crate::__real_transmute(e) }
}
}}
/// ## `cargo check` and size errors
///
/// This function reports type size errors at a later compiler phase than
/// `mem::transmute`. Consequently, size errors may not be detected by `cargo
/// check`; only by `cargo build`. Run `cargo build` to be sure that your
/// transmute is valid.
pub const fn transmute<Src, Dst>(src: Src) -> Dst
where
Src: AsBytes,
Dst: FromBytes + TransmuteFrom<Src>,
{
#[repr(C)]
union Transmute<Src, Dst> {
src: ManuallyDrop<Src>,
dst: ManuallyDrop<Dst>,
}

assert!(<Dst as TransmuteFrom<Src>>::IS_TRANSMUTABLE);

// SAFETY: This is sound because `transmute`'s bounds ensure that `Src` is
// `AsBytes`, that `Dst` is `FromBytes`, and the above assert ensures that
// `Src` is at least as many bytes as `Dst`.
unsafe { ManuallyDrop::into_inner(Transmute { src: ManuallyDrop::new(src) }.dst) }
}

/// A length- and alignment-checked reference to a byte slice which can safely
Expand Down Expand Up @@ -3013,7 +3007,7 @@ mod tests {

// Converts an `AU64` to bytes using this platform's endianness.
fn au64_to_bytes(u: AU64) -> [u8; 8] {
transmute!(u)
transmute(u)
}

// An unsized type.
Expand Down Expand Up @@ -3142,12 +3136,12 @@ mod tests {
assert_eq!(u64::read_from(&VAL_BYTES[..]), Some(VAL));
// The first 8 bytes are from `VAL_BYTES` and the second 8 bytes are all
// zeroes.
let bytes_with_prefix: [u8; 16] = transmute!([VAL_BYTES, [0; 8]]);
let bytes_with_prefix: [u8; 16] = transmute([VAL_BYTES, [0; 8]]);
assert_eq!(u64::read_from_prefix(&bytes_with_prefix[..]), Some(VAL));
assert_eq!(u64::read_from_suffix(&bytes_with_prefix[..]), Some(0));
// The first 8 bytes are all zeroes and the second 8 bytes are from
// `VAL_BYTES`
let bytes_with_suffix: [u8; 16] = transmute!([[0; 8], VAL_BYTES]);
let bytes_with_suffix: [u8; 16] = transmute([[0; 8], VAL_BYTES]);
assert_eq!(u64::read_from_prefix(&bytes_with_suffix[..]), Some(0));
assert_eq!(u64::read_from_suffix(&bytes_with_suffix[..]), Some(VAL));

Expand All @@ -3158,11 +3152,11 @@ mod tests {
assert_eq!(bytes, VAL_BYTES);
let mut bytes = [0u8; 16];
assert_eq!(VAL.write_to_prefix(&mut bytes[..]), Some(()));
let want: [u8; 16] = transmute!([VAL_BYTES, [0; 8]]);
let want: [u8; 16] = transmute([VAL_BYTES, [0; 8]]);
assert_eq!(bytes, want);
let mut bytes = [0u8; 16];
assert_eq!(VAL.write_to_suffix(&mut bytes[..]), Some(()));
let want: [u8; 16] = transmute!([[0; 8], VAL_BYTES]);
let want: [u8; 16] = transmute([[0; 8], VAL_BYTES]);
assert_eq!(bytes, want);
}

Expand All @@ -3171,9 +3165,9 @@ mod tests {
// Test that memory is transmuted as expected.
let array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7];
let array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]];
let x: [[u8; 2]; 4] = transmute!(array_of_u8s);
let x: [[u8; 2]; 4] = transmute(array_of_u8s);
assert_eq!(x, array_of_arrays);
let x: [u8; 8] = transmute!(array_of_arrays);
let x: [u8; 8] = transmute(array_of_arrays);
assert_eq!(x, array_of_u8s);

// Test that the source expression's value is forgotten rather than
Expand All @@ -3186,12 +3180,12 @@ mod tests {
panic!("PanicOnDrop::drop");
}
}
let _: () = transmute!(PanicOnDrop(()));
let _: () = transmute(PanicOnDrop(()));

// Test that `transmute!` is legal in a const context.
// Test that `transmute` is legal in a const context.
const ARRAY_OF_U8S: [u8; 8] = [0u8, 1, 2, 3, 4, 5, 6, 7];
const ARRAY_OF_ARRAYS: [[u8; 2]; 4] = [[0, 1], [2, 3], [4, 5], [6, 7]];
const X: [[u8; 2]; 4] = transmute!(ARRAY_OF_U8S);
const X: [[u8; 2]; 4] = transmute(ARRAY_OF_U8S);
assert_eq!(X, ARRAY_OF_ARRAYS);
}

Expand Down
23 changes: 3 additions & 20 deletions tests/ui-msrv/transmute-bound-dst-not-frombytes.stderr
Original file line number Diff line number Diff line change
@@ -1,22 +1,5 @@
error[E0277]: the trait bound `NotZerocopy<AU16>: FromBytes` is not satisfied
--> tests/ui-msrv/transmute-bound-dst-not-frombytes.rs:9:47
error[E0433]: failed to resolve: could not find `transmute` in `zerocopy`
--> tests/ui-msrv/transmute-bound-dst-not-frombytes.rs:9:57
|
9 | const DST_NOT_FROM_BYTES: NotZerocopy<AU16> = zerocopy::transmute!(AU16(0));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromBytes` is not implemented for `NotZerocopy<AU16>`
|
= help: the following other types implement trait `FromBytes`:
()
AU16
F32<O>
F64<O>
I128<O>
I16<O>
I32<O>
I64<O>
and $N others
note: required by a bound in `DST_NOT_FROM_BYTES::transmute`
--> tests/ui-msrv/transmute-bound-dst-not-frombytes.rs:9:47
|
9 | const DST_NOT_FROM_BYTES: NotZerocopy<AU16> = zerocopy::transmute!(AU16(0));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `DST_NOT_FROM_BYTES::transmute`
= note: this error originates in the macro `zerocopy::transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
| ^^^^^^^^^ could not find `transmute` in `zerocopy`
37 changes: 29 additions & 8 deletions tests/ui-msrv/transmute-bound-src-not-asbytes.stderr
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
error[E0277]: the trait bound `NotZerocopy<AU16>: AsBytes` is not satisfied
--> tests/ui-msrv/transmute-bound-src-not-asbytes.rs:9:32
--> tests/ui-msrv/transmute-bound-src-not-asbytes.rs:9:52
|
9 | const SRC_NOT_AS_BYTES: AU16 = zerocopy::transmute!(NotZerocopy(AU16(0)));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
9 | const SRC_NOT_AS_BYTES: AU16 = zerocopy::transmute(NotZerocopy(AU16(0)));
| ------------------- ^^^^^^^^^^^^^^^^^^^^ the trait `AsBytes` is not implemented for `NotZerocopy<AU16>`
| |
| the trait `AsBytes` is not implemented for `NotZerocopy<AU16>`
| required by a bound introduced by this call
|
= help: the following other types implement trait `AsBytes`:
Expand All @@ -17,9 +16,31 @@ error[E0277]: the trait bound `NotZerocopy<AU16>: AsBytes` is not satisfied
I32<O>
I64<O>
and $N others
note: required by a bound in `SRC_NOT_AS_BYTES::transmute`
note: required by a bound in `zerocopy::transmute`
--> src/lib.rs
|
| Src: AsBytes,
| ^^^^^^^ required by this bound in `zerocopy::transmute`

error[E0277]: the trait bound `NotZerocopy<AU16>: AsBytes` is not satisfied
--> tests/ui-msrv/transmute-bound-src-not-asbytes.rs:9:32
|
9 | const SRC_NOT_AS_BYTES: AU16 = zerocopy::transmute!(NotZerocopy(AU16(0)));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `SRC_NOT_AS_BYTES::transmute`
= note: this error originates in the macro `zerocopy::transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
9 | const SRC_NOT_AS_BYTES: AU16 = zerocopy::transmute(NotZerocopy(AU16(0)));
| ^^^^^^^^^^^^^^^^^^^ the trait `AsBytes` is not implemented for `NotZerocopy<AU16>`
|
= help: the following other types implement trait `AsBytes`:
()
AU16
F32<O>
F64<O>
I128<O>
I16<O>
I32<O>
I64<O>
and $N others
= note: required for `AU16` to implement `TransmuteFrom<NotZerocopy<AU16>>`
note: required by a bound in `zerocopy::transmute`
--> src/lib.rs
|
| Dst: FromBytes + TransmuteFrom<Src>,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `zerocopy::transmute`
37 changes: 29 additions & 8 deletions tests/ui-msrv/transmute-pointer.stderr
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
error[E0277]: the trait bound `*const usize: AsBytes` is not satisfied
--> tests/ui-msrv/transmute-pointer.rs:7:30
--> tests/ui-msrv/transmute-pointer.rs:7:50
|
7 | const POINTER_VALUE: usize = zerocopy::transmute!(&0usize as *const usize);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7 | const POINTER_VALUE: usize = zerocopy::transmute(&0usize as *const usize);
| ------------------- ^^^^^^^^^^^^^^^^^^^^^^^ the trait `AsBytes` is not implemented for `*const usize`
| |
| the trait `AsBytes` is not implemented for `*const usize`
| required by a bound introduced by this call
|
= help: the following other types implement trait `AsBytes`:
Expand All @@ -17,9 +16,31 @@ error[E0277]: the trait bound `*const usize: AsBytes` is not satisfied
i8
isize
and $N others
note: required by a bound in `POINTER_VALUE::transmute`
note: required by a bound in `zerocopy::transmute`
--> src/lib.rs
|
| Src: AsBytes,
| ^^^^^^^ required by this bound in `zerocopy::transmute`

error[E0277]: the trait bound `*const usize: AsBytes` is not satisfied
--> tests/ui-msrv/transmute-pointer.rs:7:30
|
7 | const POINTER_VALUE: usize = zerocopy::transmute!(&0usize as *const usize);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `POINTER_VALUE::transmute`
= note: this error originates in the macro `zerocopy::transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
7 | const POINTER_VALUE: usize = zerocopy::transmute(&0usize as *const usize);
| ^^^^^^^^^^^^^^^^^^^ the trait `AsBytes` is not implemented for `*const usize`
|
= help: the following other types implement trait `AsBytes`:
f32
f64
i128
i16
i32
i64
i8
isize
and $N others
= note: required for `usize` to implement `TransmuteFrom<*const usize>`
note: required by a bound in `zerocopy::transmute`
--> src/lib.rs
|
| Dst: FromBytes + TransmuteFrom<Src>,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `zerocopy::transmute`
31 changes: 24 additions & 7 deletions tests/ui-msrv/transmute-size-decrease.stderr
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
--> tests/ui-msrv/transmute-size-decrease.rs:7:32
error[E0080]: evaluation of `<[u8; 1] as zerocopy::TransmuteFrom<[u8; 2]>>::IS_TRANSMUTABLE` failed
--> src/lib.rs
|
7 | const SIZE_DECREASE: [u8; 1] = zerocopy::transmute!([0u8; 2]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| assert!(mem::size_of::<Src>() == mem::size_of::<Dst>());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: mem::size_of::<Src>() == mem::size_of::<Dst>()', $DIR/src/lib.rs:1427:9
|
= note: source type: `[u8; 2]` (16 bits)
= note: target type: `[u8; 1]` (8 bits)
= note: this error originates in the macro `zerocopy::transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)

error: any use of this value will cause an error
--> src/lib.rs
|
| assert!(<Dst as TransmuteFrom<Src>>::IS_TRANSMUTABLE);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| referenced constant has errors
| inside `zerocopy::transmute::<[u8; 2], [u8; 1]>` at $DIR/src/lib.rs:1452:13
| inside `SIZE_DECREASE` at $DIR/tests/ui-msrv/transmute-size-decrease.rs:7:32
|
::: tests/ui-msrv/transmute-size-decrease.rs:7:1
|
7 | const SIZE_DECREASE: [u8; 1] = zerocopy::transmute([0u8; 2]);
| ----------------------------
|
= note: `#[deny(const_err)]` on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
31 changes: 24 additions & 7 deletions tests/ui-msrv/transmute-size-increase.stderr
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
--> tests/ui-msrv/transmute-size-increase.rs:7:32
error[E0080]: evaluation of `<[u8; 2] as zerocopy::TransmuteFrom<[u8; 1]>>::IS_TRANSMUTABLE` failed
--> src/lib.rs
|
7 | const SIZE_INCREASE: [u8; 2] = zerocopy::transmute!([0u8; 1]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| assert!(mem::size_of::<Src>() == mem::size_of::<Dst>());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: mem::size_of::<Src>() == mem::size_of::<Dst>()', $DIR/src/lib.rs:1427:9
|
= note: source type: `[u8; 1]` (8 bits)
= note: target type: `[u8; 2]` (16 bits)
= note: this error originates in the macro `zerocopy::transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)

error: any use of this value will cause an error
--> src/lib.rs
|
| assert!(<Dst as TransmuteFrom<Src>>::IS_TRANSMUTABLE);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| referenced constant has errors
| inside `zerocopy::transmute::<[u8; 1], [u8; 2]>` at $DIR/src/lib.rs:1452:13
| inside `SIZE_INCREASE` at $DIR/tests/ui-msrv/transmute-size-increase.rs:7:32
|
::: tests/ui-msrv/transmute-size-increase.rs:7:1
|
7 | const SIZE_INCREASE: [u8; 2] = zerocopy::transmute([0u8; 1]);
| ----------------------------
|
= note: `#[deny(const_err)]` on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
25 changes: 7 additions & 18 deletions tests/ui-nightly/transmute-bound-dst-not-frombytes.stderr
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
error[E0277]: the trait bound `NotZerocopy<AU16>: FromBytes` is not satisfied
--> tests/ui-nightly/transmute-bound-dst-not-frombytes.rs:9:47
error[E0433]: failed to resolve: could not find `transmute` in `zerocopy`
--> tests/ui-nightly/transmute-bound-dst-not-frombytes.rs:9:57
|
9 | const DST_NOT_FROM_BYTES: NotZerocopy<AU16> = zerocopy::transmute!(AU16(0));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromBytes` is not implemented for `NotZerocopy<AU16>`
| ^^^^^^^^^ could not find `transmute` in `zerocopy`
|
= help: the following other types implement trait `FromBytes`:
()
AU16
F32<O>
F64<O>
I128<O>
I16<O>
I32<O>
I64<O>
and $N others
note: required by a bound in `DST_NOT_FROM_BYTES::transmute`
--> tests/ui-nightly/transmute-bound-dst-not-frombytes.rs:9:47
help: zerocopy::transmute is not a macro, but a function, try to remove `!`
|
9 - const DST_NOT_FROM_BYTES: NotZerocopy<AU16> = zerocopy::transmute!(AU16(0));
9 + const DST_NOT_FROM_BYTES: NotZerocopy<AU16> = zerocopy::transmute(AU16(0));
|
9 | const DST_NOT_FROM_BYTES: NotZerocopy<AU16> = zerocopy::transmute!(AU16(0));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `transmute`
= note: this error originates in the macro `zerocopy::transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
2 changes: 1 addition & 1 deletion tests/ui-nightly/transmute-bound-src-not-asbytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ extern crate zerocopy;
include!("../../zerocopy-derive/tests/util.rs");

// The source type must be `AsBytes`.
const SRC_NOT_AS_BYTES: AU16 = zerocopy::transmute!(NotZerocopy(AU16(0)));
const SRC_NOT_AS_BYTES: AU16 = zerocopy::transmute(NotZerocopy(AU16(0)));

fn main() {
_ = SRC_NOT_AS_BYTES;
Expand Down
Loading