[Accepted with Modifications] SE-0322: Temporary Uninitialized Buffers

Hi everyone! The review for SE-0322: Temporary Uninitialized Buffers ran from September 7 through 20, 2021. The core team has decided to accept the proposal, with the revision that the APIs use the following names instead:

public func withUnsafeTemporaryAllocation<R>(
  byteCount: Int,
  alignment: Int,
  _ body: (UnsafeMutableRawBufferPointer) throws -> R
) rethrows -> R

public func withUnsafeTemporaryAllocation<T, R>(
  of type: T.Type,
  capacity: Int,
  _ body: (UnsafeMutableBufferPointer<T>) throws -> R
) rethrows -> R

Support for adding this API was divided, but the core team believes that this functionality is worth adding to the standard library:

  • Although it is true that optimization can hoist heap allocations into stack allocations when they're known not to escape, there are limitations to doing so in practice; hoisting a dynamically-sized allocation raises the likelihood of overflowing the stack without also inserting checks that increase code size, and the optimization might undo a programmer's manual use of heap allocations to avoid known stack overflows. Having an API with the dedicated purpose of providing a scoped allocation is a clear signal that stack allocation is desired, and it's worth maybe spending some effort to make stack allocation happen.
  • Some use cases for this API may indeed be superseded by safer alternatives, such as better language support for moving values and marking them nonescaping to control their lifetimes, or fixed-sized arrays with an API like Array.init(withUnsafeUninitializedCapacity:). The core team certainly hopes that unsafe APIs become less necessary as the language evolves, but we believe the proposed API will still have a place in the standard library, particularly for situations where a dynamically-sized stack allocation is needed, or where untyped raw memory is necessary, such as to be able to temporarily store heterogeneous variably-sized data.

So we believe this functionality will continue to be a useful part of the standard library going forward. During the review, the core team also responded to concerns that the proposal allows for a fallback to heap allocation while leaving the circumstances for doing so implementation-defined. We had suggested that the APIs be augmented to take an optional threshold that the caller could provide to specify how large of an allocation they are willing to require to be on the stack. On further discussion, we have decided to revoke this guidance, and leave the threshold implementation-defined. There was significant concern that developers would not be able to effectively provide a threshold value, any more than we could agree on a threshold from first principles ourselves. We acknowledge that leaving the threshold implementation-defined runs a risk of implicit dependencies on the current implementation's behavior; however, to the degree we can, we would like to retain flexibility to adjust behavior in response to experience with the API, and strengthen the promises provided by the API as we learn from that experience.

Another topic of discussion was whether the typed variant should have an additional variant to make allocating and using one uninitialized value more convenient, equivalent to using the UnsafeMutableBufferPointer variant with a count of one and taking the baseAddress! of the single allocated value. The core team does not expect the need to manually allocate a single uninitialized value to be common, so we do not think that this proposal needs to include it. That variant could however be added by a future proposal, in an ABI-backward-compatible way, if experience shows it to be necessary.

21 Likes