[Accepted with modifications] SE-0525: Safe loading API for RawSpan

Hi all,

The review of SE-0525: Safe loading API for RawSpan concluded on April 16, 2026. The language steering group has decided the proposal should be accepted with modifications.

Detailed review of the API surface resulted in a few corrections that were already made by the author during review:

  • MutableSpan(_: consuming MutableRawSpan) needs a ConvertibleToBytes constraint
  • memcpy copies the initialization state of each byte, so storeBytes overloads constrained only to BitwiseCopyable need to be marked @unsafe

The steering group also tended to agree with concerns about having the default byte offset of 0 for load(fromByteOffset:as:) and store(fromByteOffset:as:) APIs. Even as there's precedent for this default, including on RawSpan itself, we think it's wise not to propagate this decision further. The author has agreed with proceeding with this modification.


In the course of editorial clean-up, the author has identified a few API-level bugfixes:

  • MutableSpan.init(bytes: consuming MutableRawSpan) → MutableSpan.init(mutableBytes: consuming MutableRawSpan)
  • Output(Raw)Span.append(elements:...) → Output(Raw)Span.append(elementCount:...)
  • Adding property OutputRawSpan.byteOffsets

Of these, the steering group is particularly keen on hearing feedback about the second API, given existing and currently proposed APIs on Data and (Unique|Rigid)Array with slightly different naming—append(_:count:) and append(addingCount:...), respectively. Please reply in this thread with any feedback related to this or the other API bugfixes above.

Having held open the proposal for further discussion of the renamed/added APIs, with the append(<label>:...) APIs withdrawn from this document by the author and no concerns on the other renamed/added APIs, the language steering group decided to accept the revised proposal with the modifications described above.

Thank you, as always, to all participants in this review process for helping to make Swift a better language.

Xiaodi Wu
Review Manager

5 Likes

I’m not sure this is quite “accepted with modifications”; we really do want to hold the review open on the question of naming those APIs.

4 Likes

Conformances in the standard library:

Note: any of the types in the list above which is missing a prerequisite BitwiseCopyable conformance will gain one.

Does that include the range types, or will they require another proposal?


Why doesn't ConvertibleToBytes refine BitwiseCopyable?

(I think the reason was only mentioned in one of the pitch threads.)


Should the names of the new protocols be aligned with BitwiseCopyable?

For example:

  • BitwiseStorable instead of ConvertibleToBytes
  • BitwiseLoadable instead of ConvertibleFromBytes

The FullyInhabited type alias isn't used by the current implementation.

Yup, that's a better characterization—thanks.

They're not ConvertibleFromBytes so no. Note that finally adding the missing conditional BitwiseCopyable conformances is on my short-term to-do list.

Ultimately the requirement is to ensure that every byte converted is initialized. A class reference qualifies, in this respect. With some extra compiler implementation work, String (the MemoryLayout<String>.stride-byte value) could conform to ConvertibleToBytes. Making ConvertibleToBytes a refinement of BitwiseCopyable would foreclose that possibility.

2 Likes

Does this mean the safe bitCast function would have slightly different behavior from unsafeBitCast with respect to ownership? unsafeBitCast consumes its argument, which I assume is because the argument's ownership of resources is "transferred" to the return value. For example, unsafeBitCast(array, to: Int.self) would transfer ownership of the buffer reference to the Int, making the caller responsible to later convert the Int back into an Array to decrement the reference count of the buffer. But, since bitCast is a safe function, it should probably prevent leaking, which would mean bitCast(array, to: Int.self) would not transfer ownership in the same way.

bitCast doesn't interact with ownership because it only deals in copyable values on input, and bitwise-copyable values on output.

On the naming of functions of the form

append(<label>: Int, initializingWith: (Output{Raw}Span) -> Void) -> Void

(omitting throws for brevity.)

We already have such functions on Array, ContiguousArray and Data. The label chosen there was addingCapacity, which does not work for fixed-capacity containers such as RigidArray or OutputSpan. We would prefer to have a label that works for all situations.

It is useful to note that we expect OutputSpan to conform to many of the same protocols as RigidArray, and that in time it should have a similar function as proposed by SE-0527 for insertions:

insert(<label>: Int, at index: Int, initializingWith: ...)

Personally, I like upTo n: Int, since it works for dynamic and rigid containers, append and insert operations. It's less strict than RigidArray proposes for insert's label (where under-filling means moving the tail elements twice,) but in every case there will be slight behavioural differences between functions of the same name; those need to be described by documentation. That is probably fine! Dynamic data structures can reallocate and will not trap (Array, Data & UniqueArray,) "rigid" data structures will trap on capacity overflow (OutputSpan & RigidArray,) and insertion operations have a cleanup cost when the closure under-fills that append operations do not have.

4 Likes

Would it be worth removing this default from RawSpan? (technically: adding a new deprecated overload without the parameter + removing the default value)

If you are adding conformance, it would be great to get them added to C enum wrappers (both the ones that are exposed as enums and the ones exposed as structs).

1 Like

After more discussion of the naming of append(<label>: Int, initializingWith: (OutputSpan) -> ()), we have decided to remove it from SE-0525. There is a whole family of functions of this shape, and how they are all named should probably be done in a proposal with more visibility than this one. In SE-0527 (UniqueArray and RigidArray) this function family (append, insert, replace) is essential functionality, unlike in SE-0525.

Most of the API of RigidArray will also apply to OutputSpan, so once SE-0527 is completed, there will be a clear path for new OutputSpan and OutputRawSpan API.

Thanks for the discussion everyone!

3 Likes

I'm not sure that this is the rationale, but: while the safe bitCast can turn a pointer into an integer, it can't turn an integer into a pointer. There would be a problem if bitCast "safely" handed ownership of a resource to a value that can't safely dispose of it.

2 Likes

Having held open the proposal for further discussion of the renamed/added APIs, with the append(<label>:...) APIs withdrawn from this document by the author and no concerns on the other renamed/added APIs, the language steering group decided to accept the revised proposal with the modifications described above.

Xiaodi Wu
Review Manager

3 Likes