Using `Span` on pre-26 Apple OS versions

The new Span type is described in the docs as iOS 12.2+, macOS 10.14.4+, …

However, every API I could find thus far that vends a Span (like Array) is appleOS 26.0+.

Is there a way to create a Span on older Apple OS versions?

It should be possible for most of the new standard library APIs that produce Span to take advantage of Span's back deployment library. I let the team know that they can lower the minimum OS requirement for these APIs.

3 Likes

The Span types themselves are (planned to be) available before Swift 6.2, but the properties on Array and String may also depend on bridging changes in the stdlib and Foundation that may not be backdeployable.

Cc @glessard

5 Likes

Array.span, String.utf8.span and Substring.utf8.span aren't back-deployable due to the bridging changes they depend on.

2 Likes

Thank you all for looking into this! In my case, I would want to parse binary data byte by byte, so Span<UInt8> and RawSpan would both be suitable. Currently, I am using UnsafeBufferPointer to avoid the bounds checks (redundant, since my parser checks each byte, too).[1]

So, both UnsafeBufferPointer.span and UnsafeRawBufferPointer.bytes would work for me, but are currently appleOS 26+. Or perhaps ContiguousArray.span would not be affected by Foundation bridging (also 26+ currently)?

Too bad about String.utf8.span being blocked, I would have loved to use that, too. Perhaps this is something that could be solved by a non-bridging string, like ContiguousString? Probably not in time for my use case, but to work around such bridging-blocks in the future.


  1. I know, Span does bounds checks as well, but it allows to opt out of checks on a per-access basis. ↩︎

1 Like

You can use the existing withUnsafeBufferPointer APIs to provide a partial polyfill -- however, the performance characteristics of that are too much of a trap to wholeheartedly recommend doing that. (And writing a withSpan wrapper around withUBP requires Span initializers which IIRC we do not have yet. :disappointed_face: When they arrive, they are expected to be backdeployable.)

A crucial part of the span properties is that they are borrowing storage that is owned by and physically exists somewhere in the instance -- so repeated span invocations do not come with endlessly repeating, huge bridging/etc overhead. (Some bridged NSArray/NSString instances don't use/expose contiguous storage, and for those a contiguous buffer sometimes needs to be allocated on the first invocation of span; but the buffer is then saved and transparently reused, so the properties have "amortized O(1)" complexity (in the way the stdlib uses that term).)

The old withUBP functions are of constant complexity on some instances, and linear on others: when necessary, they materialize contiguous storage in a temporary buffer, which they immediately throw away at the end of the call. This unpredictable/unreliable behavior makes them generally unusable (or, at best, tricky to use) in the performance-sensitive use cases that the unsafe buffer pointer types are supposed to enable: they are common sources of accidentally quadratic behavior and similar problems. The span properties are designed to avoid this -- but this unfortunately does have the (temporary!) cost of availability pain.

(We are playing a long game. As usual, some people will be able to immediately make use of the new facilities, why others are sadly forced to wait a while. Eventually Swift will still gain reliable, predictable, efficient primitives for everyone; it just takes a bit longer for some to start using them.)

7 Likes