Idiomatic way to pass "out slice" to C function?

Viewed 184

Some Windows APIs take slice as parameter and write to it upon return. It's similar to an out pointer but in slice form (so that caller don't need to pass an extra "length" parameter).

In cases of out pointer, I've been using MaybeUninit, which I think is the idiomatic way in Rust. However, I do not know how to use it in case of slices.

For example, many examples suggest to declare [MaybeUninit<u16>; 32], but how do I pass it to a function that accepts only &mut [u16]? I tried MaybeUninit<[u16; 32]>, but there is no way to get an uninitialized &mut T out of MaybeUninit. There is only as_mut_ptr, which is pointer, not slice.

Am I supposed to stick to let x: [u16; 32] = zeroed(); at the moment?

2 Answers

You do not canonically need MaybeUninit, you can fill a buffer array with zeroes yourself:

let x: [u16; 32] = [0u16; 32];

let res = unsafe { GetClassNameW(param0, &mut x); }

Creating a reference to an uninitialized memory is Undefined Behavior, even if the memory contents are never read. Quoting the reference, "Behavior considered undefined" (emphasis mine):

  • ...
  • Producing an invalid value, even in private fields and locals. "Producing" a value happens any time a value is assigned to or read from a place, passed to a function/primitive operation or returned from a function/primitive operation. The following values are invalid (at their respective type):
    • ...
    • A reference or Box<T> that is dangling, unaligned, or points to an invalid value.

...

Note: Uninitialized memory is also implicitly invalid for any type that has a restricted set of valid values. In other words, the only cases in which reading uninitialized memory is permitted are inside unions and in "padding" (the gaps between the fields/elements of a type).

It is not obvious this is UB: there is an active discussion about that (part of the counterarguments is to allow things like your case). However, currently it is considered UB and you should avoid it.

(Note: the "has a restricted set of valid values" is somewhat ambiguous. Whether primitive types like integers are allowed to contain uninitialized bits is also a matter of active discussion, but in this case too you should avoid it until it is settled. You may claim the reference does not agree with that, because integers do not have a restricted set of values, but this is false: one can view the set of possible values for a byte as 0-256 and the uninit byte, and in fact this is an interpretation used in many places. Integers cannot contain the uninit byte and so have a restricted set of values).

Initializing the array using mem::zeroed() is sound but uses unsafe for no reason: you can just use an initialized, i.e. [0; size], and it will be just as performant.

Related