Skip to content

Commit e574d06

Browse files
authored
Support sized-to-unsized transmute_{ref,mut}! (#2943)
Closes #2721 gherrit-pr-id: G73f67c103188ed404d0051bc140c4d0711fe0753
1 parent f28e016 commit e574d06

35 files changed

+326
-168
lines changed

src/macros.rs

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,11 @@ macro_rules! transmute {
210210
///
211211
/// # Size compatibility
212212
///
213-
/// `transmute_ref!` supports transmuting between `Sized` types or between
214-
/// unsized (i.e., `?Sized`) types. It supports any transmutation that preserves
215-
/// the number of bytes of the referent, even if doing so requires updating the
216-
/// metadata stored in an unsized "fat" reference:
213+
/// `transmute_ref!` supports transmuting between `Sized` types, between unsized
214+
/// (i.e., `?Sized`) types, and from a `Sized` type to an unsized type. It
215+
/// supports any transmutation that preserves the number of bytes of the
216+
/// referent, even if doing so requires updating the metadata stored in an
217+
/// unsized "fat" reference:
217218
///
218219
/// ```
219220
/// # use zerocopy::transmute_ref;
@@ -348,11 +349,32 @@ macro_rules! transmute_ref {
348349
} else {
349350
use $crate::util::macro_util::TransmuteRefDst;
350351
let t = $crate::util::macro_util::Wrap::new(e);
351-
// SAFETY: The `if false` branch ensures that:
352-
// - `Src: IntoBytes + Immutable`
353-
// - `Dst: FromBytes + Immutable`
354-
unsafe {
355-
t.transmute_ref()
352+
353+
if false {
354+
// This branch exists solely to force the compiler to infer the
355+
// type of `Dst` *before* it attempts to resolve the method call
356+
// to `transmute_ref` in the `else` branch.
357+
//
358+
// Without this, if `Src` is `Sized` but `Dst` is `!Sized`, the
359+
// compiler will eagerly select the inherent impl of
360+
// `transmute_ref` (which requires `Dst: Sized`) because inherent
361+
// methods take priority over trait methods. It does this before
362+
// it realizes `Dst` is `!Sized`, leading to a compile error when
363+
// it checks the bounds later.
364+
//
365+
// By calling this helper (which returns `&Dst`), we force `Dst`
366+
// to be fully resolved. By the time it gets to the `else`
367+
// branch, the compiler knows `Dst` is `!Sized`, properly
368+
// disqualifies the inherent method, and falls back to the trait
369+
// implementation.
370+
t.transmute_ref_inference_helper()
371+
} else {
372+
// SAFETY: The outer `if false` branch ensures that:
373+
// - `Src: IntoBytes + Immutable`
374+
// - `Dst: FromBytes + Immutable`
375+
unsafe {
376+
t.transmute_ref()
377+
}
356378
}
357379
}
358380
}}
@@ -383,10 +405,11 @@ macro_rules! transmute_ref {
383405
///
384406
/// # Size compatibility
385407
///
386-
/// `transmute_mut!` supports transmuting between `Sized` types or between
387-
/// unsized (i.e., `?Sized`) types. It supports any transmutation that preserves
388-
/// the number of bytes of the referent, even if doing so requires updating the
389-
/// metadata stored in an unsized "fat" reference:
408+
/// `transmute_mut!` supports transmuting between `Sized` types, between unsized
409+
/// (i.e., `?Sized`) types, and from a `Sized` type to an unsized type. It
410+
/// supports any transmutation that preserves the number of bytes of the
411+
/// referent, even if doing so requires updating the metadata stored in an
412+
/// unsized "fat" reference:
390413
///
391414
/// ```
392415
/// # use zerocopy::transmute_mut;
@@ -503,7 +526,26 @@ macro_rules! transmute_mut {
503526
#[allow(unused)]
504527
use $crate::util::macro_util::TransmuteMutDst as _;
505528
let t = $crate::util::macro_util::Wrap::new(e);
506-
t.transmute_mut()
529+
if false {
530+
// This branch exists solely to force the compiler to infer the type
531+
// of `Dst` *before* it attempts to resolve the method call to
532+
// `transmute_mut` in the `else` branch.
533+
//
534+
// Without this, if `Src` is `Sized` but `Dst` is `!Sized`, the
535+
// compiler will eagerly select the inherent impl of `transmute_mut`
536+
// (which requires `Dst: Sized`) because inherent methods take
537+
// priority over trait methods. It does this before it realizes
538+
// `Dst` is `!Sized`, leading to a compile error when it checks the
539+
// bounds later.
540+
//
541+
// By calling this helper (which returns `&mut Dst`), we force `Dst`
542+
// to be fully resolved. By the time it gets to the `else` branch,
543+
// the compiler knows `Dst` is `!Sized`, properly disqualifies the
544+
// inherent method, and falls back to the trait implementation.
545+
t.transmute_mut_inference_helper()
546+
} else {
547+
t.transmute_mut()
548+
}
507549
}}
508550
}
509551

@@ -1196,6 +1238,13 @@ mod tests {
11961238
const X: &'static [[u8; 2]; 4] = transmute_ref!(&ARRAY_OF_U8S);
11971239
assert_eq!(*X, ARRAY_OF_ARRAYS);
11981240

1241+
// Test sized -> unsized transmutation.
1242+
let array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7];
1243+
let array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]];
1244+
let slice_of_arrays = &array_of_arrays[..];
1245+
let x: &[[u8; 2]] = transmute_ref!(&array_of_u8s);
1246+
assert_eq!(x, slice_of_arrays);
1247+
11991248
// Before 1.61.0, we can't define the `const fn transmute_ref` function
12001249
// that we do on and after 1.61.0.
12011250
#[cfg(no_zerocopy_generic_bounds_in_const_fn_1_61_0)]
@@ -1445,6 +1494,13 @@ mod tests {
14451494
let slice_dst_small = SliceDst::<U16, u8>::mut_from_bytes(&mut bytes[..]).unwrap();
14461495
let x: &mut SliceDst<U16, u8> = transmute_mut!(slice_dst_big);
14471496
assert_eq!(x, slice_dst_small);
1497+
1498+
// Test sized -> unsized transmutation.
1499+
let mut array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7];
1500+
let mut array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]];
1501+
let slice_of_arrays = &mut array_of_arrays[..];
1502+
let x: &mut [[u8; 2]] = transmute_mut!(&mut array_of_u8s);
1503+
assert_eq!(x, slice_of_arrays);
14481504
}
14491505

14501506
#[test]

src/util/macro_util.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,17 @@ impl<Src, Dst> Wrap<Src, Dst> {
742742
}
743743
}
744744

745+
impl<'a, Src, Dst> Wrap<&'a Src, &'a Dst>
746+
where
747+
Src: ?Sized,
748+
Dst: ?Sized,
749+
{
750+
#[allow(clippy::must_use_candidate, clippy::missing_inline_in_public_items, clippy::empty_loop)]
751+
pub const fn transmute_ref_inference_helper(self) -> &'a Dst {
752+
loop {}
753+
}
754+
}
755+
745756
impl<'a, Src, Dst> Wrap<&'a Src, &'a Dst> {
746757
/// # Safety
747758
/// The caller must guarantee that:
@@ -782,6 +793,17 @@ impl<'a, Src, Dst> Wrap<&'a Src, &'a Dst> {
782793
}
783794
}
784795

796+
impl<'a, Src, Dst> Wrap<&'a mut Src, &'a mut Dst>
797+
where
798+
Src: ?Sized,
799+
Dst: ?Sized,
800+
{
801+
#[allow(clippy::must_use_candidate, clippy::missing_inline_in_public_items, clippy::empty_loop)]
802+
pub fn transmute_mut_inference_helper(self) -> &'a mut Dst {
803+
loop {}
804+
}
805+
}
806+
785807
impl<'a, Src, Dst> Wrap<&'a mut Src, &'a mut Dst> {
786808
/// Transmutes a mutable reference of one type to a mutable reference of
787809
/// another type.
@@ -825,7 +847,7 @@ pub trait TransmuteRefDst<'a> {
825847

826848
impl<'a, Src: ?Sized, Dst: ?Sized> TransmuteRefDst<'a> for Wrap<&'a Src, &'a Dst>
827849
where
828-
Src: KnownLayout<PointerMetadata = usize> + IntoBytes + Immutable,
850+
Src: KnownLayout + IntoBytes + Immutable,
829851
Dst: KnownLayout<PointerMetadata = usize> + FromBytes + Immutable,
830852
{
831853
type Dst = Dst;
@@ -858,7 +880,7 @@ pub trait TransmuteMutDst<'a> {
858880

859881
impl<'a, Src: ?Sized, Dst: ?Sized> TransmuteMutDst<'a> for Wrap<&'a mut Src, &'a mut Dst>
860882
where
861-
Src: KnownLayout<PointerMetadata = usize> + FromBytes + IntoBytes,
883+
Src: KnownLayout + FromBytes + IntoBytes,
862884
Dst: KnownLayout<PointerMetadata = usize> + FromBytes + IntoBytes,
863885
{
864886
type Dst = Dst;

tests/ui-msrv/transmute-mut-const.stderr

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ error[E0015]: calls in constants are limited to constant functions, tuple struct
2929
|
3030
= note: this error originates in the macro `transmute_mut` (in Nightly builds, run with -Z macro-backtrace for more info)
3131

32+
error[E0015]: calls in constants are limited to constant functions, tuple structs and tuple variants
33+
--> tests/ui-msrv/transmute-mut-const.rs:18:37
34+
|
35+
18 | const CONST_CONTEXT: &mut [u8; 2] = transmute_mut!(&mut ARRAY_OF_U8S);
36+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
37+
|
38+
= note: this error originates in the macro `transmute_mut` (in Nightly builds, run with -Z macro-backtrace for more info)
39+
3240
error[E0716]: temporary value dropped while borrowed
3341
--> tests/ui-msrv/transmute-mut-const.rs:18:57
3442
|

tests/ui-msrv/transmute-mut-dst-not-a-reference.stderr

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,13 @@ error[E0308]: mismatched types
77
= note: expected type `usize`
88
found mutable reference `&mut _`
99
= note: this error originates in the macro `transmute_mut` (in Nightly builds, run with -Z macro-backtrace for more info)
10+
11+
error[E0308]: mismatched types
12+
--> tests/ui-msrv/transmute-mut-dst-not-a-reference.rs:15:36
13+
|
14+
15 | const DST_NOT_A_REFERENCE: usize = transmute_mut!(&mut 0u8);
15+
| ^^^^^^^^^^^^^^^^^^^^^^^^ expected `usize`, found `&mut _`
16+
|
17+
= note: expected type `usize`
18+
found mutable reference `&mut _`
19+
= note: this error originates in the macro `transmute_mut` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui-msrv/transmute-mut-dst-unsized.rs

Lines changed: 0 additions & 1 deletion
This file was deleted.

tests/ui-msrv/transmute-mut-dst-unsized.stderr

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
error[E0271]: type mismatch resolving `<[u8; 1] as KnownLayout>::PointerMetadata == usize`
1+
error[E0599]: the method `transmute_mut` exists for struct `Wrap<&mut [u8], &mut [u8; 1]>`, but its trait bounds were not satisfied
22
--> tests/ui-msrv/transmute-mut-src-unsized.rs:15:35
33
|
44
15 | const SRC_UNSIZED: &mut [u8; 1] = transmute_mut!(&mut [0u8][..]);
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `usize`
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method cannot be called on `Wrap<&mut [u8], &mut [u8; 1]>` due to unsatisfied trait bounds
66
|
7-
= note: required because of the requirements on the impl of `TransmuteMutDst<'_>` for `Wrap<&mut [u8], &mut [u8; 1]>`
7+
::: src/util/macro_util.rs
8+
|
9+
| pub struct Wrap<Src, Dst>(pub Src, pub PhantomData<Dst>);
10+
| --------------------------------------------------------- doesn't satisfy `Wrap<&mut [u8], &mut [u8; 1]>: TransmuteMutDst`
11+
|
12+
= note: the following trait bounds were not satisfied:
13+
`[u8]: Sized`
14+
`<[u8; 1] as KnownLayout>::PointerMetadata = usize`
15+
which is required by `Wrap<&mut [u8], &mut [u8; 1]>: TransmuteMutDst`
816
= note: this error originates in the macro `transmute_mut` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui-msrv/transmute-ref-dst-mutable.stderr

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,13 @@ error[E0308]: mismatched types
2727
= note: expected mutable reference `&mut u8`
2828
found reference `&_`
2929
= note: this error originates in the macro `transmute_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
30+
31+
error[E0308]: mismatched types
32+
--> tests/ui-msrv/transmute-ref-dst-mutable.rs:16:22
33+
|
34+
16 | let _: &mut u8 = transmute_ref!(&0u8);
35+
| ^^^^^^^^^^^^^^^^^^^^ types differ in mutability
36+
|
37+
= note: expected mutable reference `&mut u8`
38+
found reference `&_`
39+
= note: this error originates in the macro `transmute_ref` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui-msrv/transmute-ref-dst-not-a-reference.stderr

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,13 @@ error[E0308]: mismatched types
2727
= note: expected type `usize`
2828
found reference `&_`
2929
= note: this error originates in the macro `transmute_ref` (in Nightly builds, run with -Z macro-backtrace for more info)
30+
31+
error[E0308]: mismatched types
32+
--> tests/ui-msrv/transmute-ref-dst-not-a-reference.rs:15:36
33+
|
34+
15 | const DST_NOT_A_REFERENCE: usize = transmute_ref!(&0u8);
35+
| ^^^^^^^^^^^^^^^^^^^^ expected `usize`, found reference
36+
|
37+
= note: expected type `usize`
38+
found reference `&_`
39+
= note: this error originates in the macro `transmute_ref` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui-msrv/transmute-ref-dst-unsized.rs

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)