Temporary replacement for still-unstable MaybeUninit::array_assume_init()

I have an array for which MaybeUninit::array_assume_init() would be a perfect fit:
an input array of [MaybeUninit<T>; LEN] and from that I need an [T; LEN] for some const generic LEN.
Further constraints of note: T doesn't necessarily implement the Clone trait. So aside from it being potentially inefficient, element-wise cloning is not an option.

The issue is that MaybeUninit::array_assume_init() is still unstable.
I have looked at its source code, and that uses intrinsics, so copying the code, assuming the risk, and calling it a day won't work either.
What can I use as a workaround until it becomes stable?

1 Like

I'm pretty sure just using a transmute_copy would be fine here:

unsafe fn array_assume_init<T, const N: usize>(array: [MaybeUninit<T>; N]) -> [T; N] {
    mem::transmute_copy(&array)
}

The two arrays are guaranteed to have the same layout, and destructors won't run twice because MaybeUninit does not run destructors.

3 Likes

The only usage of intrinsics there is to ensure that type [T; N] is inhabited. If this is guaranteed by your code, you can just ignore it and use only the second line, i.e. pointer casts.

3 Likes

The mem::transmute_copy(&array) way meshes slightly more nicely with my code, but it's good to see there are multiple viable solutions, should I ever need to switch.

Thanks for the answers!

This makes me worry that you may be inlining the transmute: transmute calls ought to be wrapped within a tiny function shim defining the signature very explicitly, since otherwise transmutation of inner lifetimes contained within T can happen.

4 Likes

Thanks for the heads-up, I'll do that as soon as I have the time.

For my information: why exactly is inlining a bad idea in this case? Does it have something to do with any relevant checks potentially being elided as part of some optimization pass after the inlining itself?

It is because, by inlining it, you are not explicitly writing down the target of the transmutation, which means that you may accidentally transmute it into a wrong type. In particular, this can be difficult to catch when lifetimes are involved, e.g. you might accidentally transmute from [MaybeUninit<&'short str>; N] to [&'long str; N]. Similarly, you might accidentally have a length mismatch and transmute from [MaybeUninit<T>; 10] to [T; 5].

Errors like this are not possible with an explicit function boundary as you must write down the types to define the function.

2 Likes

I see, thanks for the information.

I do believe none of those issues are at play (no borrows and thus no lifetime transmute, and in all arrays the same const generic LEN is used so there's no mismatch there either). However, I'll add it as a precaution. Better safe than sorry, especially since the performance impact of the extra fn is negligible.

1 Like

In general, it is considered idiomatic to always explicitly write down the types on all transmutes.

It would be inlined, so there isn't going to be any difference.

Well I put a #[inline(never)] attribute on the fn, so no.
Given the posts above, I figured that if inlining this fn was okay, then I might as well use the transmute fn directly with the turbofish operator to be explicit about the argument and output types.

I think there's some confusion with the word "inline" here. Yandros and Alice were referring to manual inlining - using transmute_copy directly. There's no problem with compiler-level inlining, you can mark that function #[inline(always)] if you want.

This is a bad idea for the reasons mentioned. It's impossible to be explicit about types with local lifetimes, because the lifetimes are anonymous. Also, explicitly specifying types is not always possible, for example with impl Trait types.

Defining a specialized function avoids all of this, and it also can make it easier to transition to using the real MaybeUninit::array_assume_init in the future; just search for any instances of calling array_assume_init in your codebase and replace them. With transmute_copy it's more generic so this task would be harder.

2 Likes

Indeed, I understand that. Perhaps I should've made clear that that was meant as an argument against inlining it manually.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.