将 MaybeUninit<[T; N]> 转换为 [MaybeUninit<T>; N] 是否可行?

7
以下代码是否正确?
#![feature(maybe_uninit)]
use std::mem;
const N: usize = 2; // or another number
type T = String; // or any other type

fn main() {
    unsafe {
        // create an uninitialized array
        let t: mem::MaybeUninit<[T; N]> = mem::MaybeUninit::uninitialized();
        // convert it to an array of uninitialized values
        let mut t: [mem::MaybeUninit<T>; N] = mem::transmute(t);
        // initialize the values
        t[0].set("Hi".to_string());
        t[1].set("there".to_string());
        // use the values
        println!("{} {}", t[0].get_ref(), t[1].get_ref());
        // drop the values
        mem::replace(&mut t[0], mem::MaybeUninit::uninitialized()).into_initialized();
        mem::replace(&mut t[1], mem::MaybeUninit::uninitialized()).into_initialized();
    }
}

我应该注意到 miri 没有遇到任何问题。
1个回答

6
更正:下面的答案在一般情况下仍然有效,但在MaybeUninit的情况下,有一些关于内存布局的方便特殊情况,使得这样做实际上是安全的:
首先,MaybeUninit的文档有一个布局部分,其中指出:

MaybeUninit<T>保证具有与T相同的大小和对齐方式。

其次,语言参考文档中关于数组布局的说明如下:

数组的布局是这样的:数组的第n个元素从数组的起始位置偏移了n *类型大小字节。一个[T; n]数组的大小为size_of::<T>() * n,并且具有与T相同的对齐方式。

这意味着MaybeUninit<[T; n]>的布局和[MaybeUninit<T>; n]的布局相同。

原始回答:

据我所知,这是可能有效但不保证的事情之一,并且可能受到编译器特定或平台特定行为的影响。

MaybeUninit当前源代码中定义如下:

#[allow(missing_debug_implementations)]
#[unstable(feature = "maybe_uninit", issue = "53491")]
pub union MaybeUninit<T> {
    uninit: (),
    value: ManuallyDrop<T>,
}

由于它没有标记#[repr]属性(与ManuallyDrop相反),因此它处于默认表示形式,关于该参考如下所述:

没有repr属性的名义类型具有默认表示。非正式地说,这种表示也称为Rust表示。

此表示不保证数据布局。

为了从Wrapper<T>转换为[Wrapper<T>],必须满足Wrapper<T>的内存布局恰好与T的内存布局完全相同。对于许多包装器,例如前面提到的ManuallyDrop,它们通常会被标记为#[repr(transparent)]属性。

但在这种情况下,这并不一定是正确的。由于()是一种零大小类型,编译器很可能会为TMaybeUninit<T>使用相同的内存布局(这就是为什么它对您有效的原因),但也有可能编译器决定使用其他内存布局(例如为了优化),在这种情况下,转换将不再起作用。
作为一个具体的例子,编译器可能选择使用以下内存布局 MaybeUninit<T>:
+---+---+...+---+
| T         | b |     where b is "is initialized" flag
+---+---+...+---+

根据上述引用,编译器被允许这样做。在这种情况下,[MaybeUninit<T>]MaybeUninit<[T]>具有不同的内存布局,因为MaybeUninit<[T]>为整个数组只有一个b,而[MaybeUninit<T>]则为数组中的每个MaybeUninit<T>都有一个b
MaybeUninit<[T]>:
+---+...+---+---+...+---+...+---+...+---+---+
| T[0]      | T[1]      | … | T[n-1]    | b |
+---+...+---+---+...+---+...+---+...+---+---+
Total size: n * size_of::<T>() + 1

[MaybeUninit<T>]
+---+...+---+----+---+...+---+----+...+---+...+---+------+
| T[0]      |b[0]| T[1]      |b[1]| … | T[n-1]    |b[n-1]|
+---+...+---+----+---+...+---+----+...+---+...+---+------+
Total size: (n + 1) * size_of::<T>()

如果我使用 MaybeUninit<[MaybeUninit<T>]> 会有什么区别吗? - llogiq
1
@llogiq 不应该。我已经添加了一个具体的例子,试图澄清我在这里的意思。 - Frxstrem
@llogiq 看来我最初在撰写答案时忽略了 MaybeUninit 文档的一部分。在 MaybeUninit 的特定情况下,似乎我最初是错误的,我已经更新了我的答案以反映这一点。 - Frxstrem
我认为自 Rust 1.37 起,MaybeUninit 已经被标记为 #[repr(transparent)]:https://github.com/rust-lang/rust/blob/master/RELEASES.md#libraries-25 - Jack O'Connor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接