Skip to content

Commit a1f8769

Browse files
committed
Support sized-to-unsized transmute_{ref,mut}!
Closes #2721 gherrit-pr-id: G73f67c103188ed404d0051bc140c4d0711fe0753
1 parent bffdaed commit a1f8769

2 files changed

Lines changed: 68 additions & 13 deletions

File tree

src/macros.rs

Lines changed: 66 additions & 11 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;
@@ -351,8 +352,28 @@ macro_rules! transmute_ref {
351352
// SAFETY: The `if false` branch ensures that:
352353
// - `Src: IntoBytes + Immutable`
353354
// - `Dst: FromBytes + Immutable`
354-
unsafe {
355-
t.transmute_ref()
355+
if false {
356+
// This branch exists solely to force the compiler to infer the
357+
// type of `Dst` *before* it attempts to resolve the method call
358+
// to `transmute_ref` in the `else` branch.
359+
//
360+
// Without this, if `Src` is `Sized` but `Dst` is `!Sized`, the
361+
// compiler will eagerly select the inherent impl of
362+
// `transmute_ref` (which requires `Dst: Sized`) because inherent
363+
// methods take priority over trait methods. It does this before
364+
// it realizes `Dst` is `!Sized`, leading to a compile error when
365+
// it checks the bounds later.
366+
//
367+
// By calling this helper (which returns `&Dst`), we force `Dst`
368+
// to be fully resolved. By the time it gets to the `else`
369+
// branch, the compiler knows `Dst` is `!Sized`, properly
370+
// disqualifies the inherent method, and falls back to the trait
371+
// implementation.
372+
t.transmute_ref_inference_helper()
373+
} else {
374+
unsafe {
375+
t.transmute_ref()
376+
}
356377
}
357378
}
358379
}}
@@ -383,10 +404,11 @@ macro_rules! transmute_ref {
383404
///
384405
/// # Size compatibility
385406
///
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:
407+
/// `transmute_mut!` supports transmuting between `Sized` types, between unsized
408+
/// (i.e., `?Sized`) types, and from a `Sized` type to an unsized type. It
409+
/// supports any transmutation that preserves the number of bytes of the
410+
/// referent, even if doing so requires updating the metadata stored in an
411+
/// unsized "fat" reference:
390412
///
391413
/// ```
392414
/// # use zerocopy::transmute_mut;
@@ -503,7 +525,26 @@ macro_rules! transmute_mut {
503525
#[allow(unused)]
504526
use $crate::util::macro_util::TransmuteMutDst as _;
505527
let t = $crate::util::macro_util::Wrap::new(e);
506-
t.transmute_mut()
528+
if false {
529+
// This branch exists solely to force the compiler to infer the type
530+
// of `Dst` *before* it attempts to resolve the method call to
531+
// `transmute_mut` in the `else` branch.
532+
//
533+
// Without this, if `Src` is `Sized` but `Dst` is `!Sized`, the
534+
// compiler will eagerly select the inherent impl of `transmute_mut`
535+
// (which requires `Dst: Sized`) because inherent methods take
536+
// priority over trait methods. It does this before it realizes
537+
// `Dst` is `!Sized`, leading to a compile error when it checks the
538+
// bounds later.
539+
//
540+
// By calling this helper (which returns `&mut Dst`), we force `Dst`
541+
// to be fully resolved. By the time it gets to the `else` branch,
542+
// the compiler knows `Dst` is `!Sized`, properly disqualifies the
543+
// inherent method, and falls back to the trait implementation.
544+
t.transmute_mut_inference_helper()
545+
} else {
546+
t.transmute_mut()
547+
}
507548
}}
508549
}
509550

@@ -1242,6 +1283,13 @@ mod tests {
12421283
const X: &'static [[u8; 2]; 4] = transmute_ref!(&ARRAY_OF_U8S);
12431284
assert_eq!(*X, ARRAY_OF_ARRAYS);
12441285

1286+
// Test sized -> unsized transmutation.
1287+
let array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7];
1288+
let array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]];
1289+
let slice_of_arrays = &array_of_arrays[..];
1290+
let x: &[[u8; 2]] = transmute_ref!(&array_of_u8s);
1291+
assert_eq!(x, slice_of_arrays);
1292+
12451293
// Before 1.61.0, we can't define the `const fn transmute_ref` function
12461294
// that we do on and after 1.61.0.
12471295
#[cfg(no_zerocopy_generic_bounds_in_const_fn_1_61_0)]
@@ -1533,6 +1581,13 @@ mod tests {
15331581
let slice_dst_small = SliceDst::<U16, u8>::mut_from_bytes(&mut bytes[..]).unwrap();
15341582
let x: &mut SliceDst<U16, u8> = transmute_mut!(slice_dst_big);
15351583
assert_eq!(x, slice_dst_small);
1584+
1585+
// Test sized -> unsized transmutation.
1586+
let mut array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7];
1587+
let mut array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]];
1588+
let slice_of_arrays = &mut array_of_arrays[..];
1589+
let x: &mut [[u8; 2]] = transmute_mut!(&mut array_of_u8s);
1590+
assert_eq!(x, slice_of_arrays);
15361591
}
15371592

15381593
#[test]

src/util/macro_util.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,7 @@ pub trait TransmuteRefDst<'a> {
827827

828828
impl<'a, Src: ?Sized, Dst: ?Sized> TransmuteRefDst<'a> for Wrap<&'a Src, &'a Dst>
829829
where
830-
Src: KnownLayout<PointerMetadata = usize> + IntoBytes + Immutable,
830+
Src: KnownLayout + IntoBytes + Immutable,
831831
Dst: KnownLayout<PointerMetadata = usize> + FromBytes + Immutable,
832832
{
833833
type Dst = Dst;
@@ -860,7 +860,7 @@ pub trait TransmuteMutDst<'a> {
860860

861861
impl<'a, Src: ?Sized, Dst: ?Sized> TransmuteMutDst<'a> for Wrap<&'a mut Src, &'a mut Dst>
862862
where
863-
Src: KnownLayout<PointerMetadata = usize> + FromBytes + IntoBytes,
863+
Src: KnownLayout + FromBytes + IntoBytes,
864864
Dst: KnownLayout<PointerMetadata = usize> + FromBytes + IntoBytes,
865865
{
866866
type Dst = Dst;

0 commit comments

Comments
 (0)