使用 const 泛型将可变指针数组转换为可变引用

Converting array of mutable pointers to mutable references with const generics

假设我保证(通过未指定的方式)我有一个 [*mut T; N] 类型的数组,其中包含 N 有效且不相交的 可变指针,所有生命周期 'a。如何将其转换为可变引用数组 [&'a mut T; N]?

也就是说,我该如何实现这个功能?

unsafe fn ptrs_to_refs<'a, T, const N: usize>(ptrs: [*mut T; N]) -> [&'a mut T; N] {
    // What goes here?
}

我不是特别在寻找使用分配步骤的实现(例如使用 Vec)。


因为 min_const_generics 在 Rust 1.51 中是 set to stabilize,这个问题针对的是假设的稳定 Rust,它也有 min_const_generics。我正在寻找符合这些要求的答案,不使用其他不稳定的功能。

例如使用 array_map 功能,可以简单地

unsafe fn ptrs_to_refs<'a, T, const N: usize>(ptrs: [*mut T; N]) -> [&'a mut T; N] {
   arr.map(|ptr| unsafe { &mut *ptr })
}

但是因为它没有设置为稳定到 1.51(据我所知)我不想使用它。

理想情况下,您只需使用 std::mem::transmute here, as references and pointers have the same memory layout. Unfortunately, std::mem::transmute doesn't work on generic arrays, even when the two types have the same layout. However, you can use union to work around this, as using a #[repr(C)] union is analogous to transmute:

#[repr(C)]
union Transmute<'a, T, const N: usize> {
    ptr_arr: ManuallyDrop<[*mut T; N]>,
    ref_arr: ManuallyDrop<[&'a mut T; N]>,
}

unsafe fn ptrs_to_refs<'a, T, const N: usize>(ptrs: [*mut T; N]) -> [&'a mut T; N] {
    let u = Transmute::<'a, T, N> { ptr_arr: ManuallyDrop::new(ptrs) };
    ManuallyDrop::into_inner(u.ref_arr)
}

您也可以使用 std::mem::transmute_copy,它在这里有效地执行相同的操作:

unsafe fn ptrs_to_refs<'a, T, const N: usize>(ptrs: [*mut T; N]) -> [&'a mut T; N] {
    std::mem::transmute_copy(&ptrs)
}

std::mem::transmute 是我首先要到达的地方,但目前还行不通;有关详细信息,请参阅 Const generics: Generic array transmutes do not work #61956

在此期间,您可以use a union as a workaround, since pointers and references have the same layout:

use core::mem::ManuallyDrop;

unsafe fn ptrs_to_refs<'a, T, const N: usize>(ptrs: [*mut T; N]) -> [&'a mut T; N] {
    union X<'a, T, const N: usize> {
        a_raw: ManuallyDrop<[*mut T; N]>,
        a_ref: ManuallyDrop<[&'a mut T; N]>,
    }

    let a_raw = ManuallyDrop::new(ptrs);
    let x = X { a_raw };
    ManuallyDrop::into_inner(x.a_ref)
}

您应该也可以使用 transmute_copy 达到同样的效果:

unsafe fn ptrs_to_refs<'a, T, const N: usize>(ptrs: [*mut T; N]) -> [&'a mut T; N] {
    std::mem::transmute_copy(&ptrs)
}

改编 MaybeUninit documentation 中的示例:

unsafe fn ptrs_to_refs<'a, T, const N: usize>(ptrs: [*mut T; N]) -> [&'a mut T; N] {
    // Create an uninitialized array of `MaybeUninit`. The `assume_init` is
    // safe because the type we are claiming to have initialized here is a
    // bunch of `MaybeUninit`s, which do not require initialization.
    let mut refs: [MaybeUninit<&'a mut T>; N] = MaybeUninit::uninit().assume_init();
    
    // Dropping a `MaybeUninit` does nothing. Thus using raw pointer
    // assignment instead of `ptr::write` does not cause the old
    // uninitialized value to be dropped.
    for i in 0..N {
        refs[i] = MaybeUninit::new(&mut *ptrs[i]);
    }
    
    std::mem::transmute_copy::<_, [&'a mut T; N]>(&refs)
}

这个答案不依赖于将 [*mut T; N] 转换为 [&mut T; N] 是否安全,而是仅在正确初始化后 MaybeUninit<T> 转换为 T