0

I'm initializing an array using code called through FFI and thus I'm using an array if MaybeUninit to represent that data. It looks something like this:

use std::mem::MaybeUninit;

#[allow(unused)]
fn init_with_ffi<T>(data: &mut [T]) {
    todo!();
}

fn get_array<T, const D: usize>() -> [T; D] {
    let mut my_array: [MaybeUninit<T>; D] = unsafe {
        MaybeUninit::uninit().assume_init()
    };
    init_with_ffi(&mut my_array[..]);
    unsafe {
        *(&mut my_array as *mut [MaybeUninit<T>; D] as *mut [T; D])
    }
}

This is similar to one of the examples presented in the docs for MaybeUninit but generic instead. I'm having to use raw pointers here since std::mem::transmute does not work with arrays of a generic size.

However, I'm getting the following error:

error[E0508]: cannot move out of type `[T; D]`, a non-copy array
  --> src/main.rs:14:9
   |
14 |         *(&mut my_array as *mut [MaybeUninit<T>; D] as *mut [T; D])
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |         |
   |         cannot move out of here
   |         move occurs because value has type `[T; D]`, which does not implement the `Copy` trait

This error message makes no sense to me. It would make sense if I was trying to copy a non-copy array or if I was trying to move ONE of the values out of a non-copy array. But I'm trying to move the entire array and my impression has always been that this should be possible, no?

Emil Eriksson
  • 2,110
  • 1
  • 21
  • 31
  • If you can use nightly, you'll have access to [`array_assume_init`](https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#method.array_assume_init). – BallpointBen Aug 15 '23 at 19:04
  • I can also use `my_array.map(|x| x.assume_init())` but my question was more about why the above doesn't work. – Emil Eriksson Aug 15 '23 at 19:19

1 Answers1

0

Your code is effectively the same as:

let ptr: *mut [T;D] = &mut my_array as *mut [MaybeUninit<T>; D] as *mut [T; D];
*ptr

Which would attempt to copy, not move, the data out of the pointer - hence your issue

The usual solution, demonstrated in the Initializing an array element-by-element section in the MaybeUninit docs, is using std::mem::transmute instead, but currently it does not work with const generics.

Instead, you can use read to copy the elements from the MaybeUninit<T> array to a new T array. In the general case, you would have to worry about duplicating a T and dropping the same contents twice, but since MaybeUninit doesn't call drop on its containing data when it itself is dropped, it should be safe. Otherwise you can use std::mem::forget on the original array to force not calling the drop method.

Colonel Thirty Two
  • 23,953
  • 8
  • 45
  • 85
  • https://github.com/rust-lang/rust/issues/61956 – Emil Eriksson Aug 15 '23 at 19:19
  • Try `ptr.read()` then. It should be safe to copy the array since `MaybeUninit` does not drop the contents when going out of scope. – Colonel Thirty Two Aug 15 '23 at 19:20
  • @EmilEriksson in fact, that's literally what "horribly error-prone workaround" section in the bug you posted does. – Colonel Thirty Two Aug 15 '23 at 19:28
  • Yeah, after tons of googling I have learned that one cannot move out of a dereferences pointer just like that it seems, you need to use `ptr::read`. The error messages gives no indication of this and I found it very confusing. – Emil Eriksson Aug 15 '23 at 19:30
  • @EmilEriksson FWIW you can't move out of a normal `&T` reference either. Pointers operate similarly in that regard - you need to use the `read` and `write` methods to use them in a more low-level manner. – Colonel Thirty Two Aug 15 '23 at 19:33
  • 1
    I realize now why that wouldn't even make sense... if you could move out of even a mutable reference, you would leave an invalid value for the owner of the value. – Emil Eriksson Aug 15 '23 at 19:43
  • Prefer `transmute_copy()` to `read()`. – Chayim Friedman Aug 15 '23 at 20:05
  • @ChayimFriedman I assume `transmute_copy` works because it doesn't care about the potential size difference between `Src` and `Dst` and wouldn't fail in the same way that `transmute` would? In this case I don't think it would be needed since `MaybeUninit` prevents dropping but otherwise I guess that I would also need to `mem::forget` the old value. – Emil Eriksson Aug 16 '23 at 07:42