[swift-evolution] [Pitch] Normalize Slice Types for Unsafe Buffers

Andrew Trick atrick at apple.com
Mon Dec 12 16:36:41 CST 2016


> On Dec 9, 2016, at 11:50 AM, Dave Abrahams via swift-evolution <swift-evolution at swift.org> wrote:
> 
> 
> on Fri Dec 09 2016, Andrew Trick <swift-evolution at swift.org> wrote:
> 
>>> On Dec 9, 2016, at 10:27 AM, Dave Abrahams via swift-evolution
>> <swift-evolution at swift.org> wrote:
>>> 
>>> 
>>> on Thu Dec 08 2016, Xiaodi Wu <xiaodi.wu-AT-gmail.com <http://xiaodi.wu-at-gmail.com/>> wrote:
>>> 
>> 
>>>> On Thu, Dec 8, 2016 at 6:53 PM, Ben Cohen via swift-evolution <
>>>> swift-evolution at swift.org> wrote:
>>>> 
>>>>> 
>>>>> On Dec 8, 2016, at 4:35 PM, Jordan Rose via swift-evolution <
>>>>> swift-evolution at swift.org> wrote:
>>>>> 
>>>>> Um, Sequence doesn’t have a subscript (or indexes). Sequences are
>>>>> single-pass. So if this is important, it needs to stay a Collection.
>>>>> 
>>>>> 
>>>>> Just because something fulfills one of the requirements of a Collection
>>>>> does not mean it should be one. It needs to tick all the boxes before its
>>>>> allowed to be elevated.
>>>>> 
>>>>> But it’s still allowed to have subscripts (UnsafePointer has subscripting
>>>>> but isn’t a collection) or be multi-pass (strides are multiples but are
>>>>> only sequences). That’s OK
>>>>> 
>>>>> In this case, yes it’s multi-pass, yes it has a subscript, but no it isn’t
>>>>> a collection because it doesn’t meet the requirements for slicing i.e. that
>>>>> indices of the slice be indices of the parent.
>>>>> (relatedly… it appears this requirement is documented on the concrete
>>>>> Slice type rather than on Collection… which is a documentation bug we
>>>>> should fix).
>>>>> 
>>>> 
>>>> If this is indeed a requirement for Collection, then my vote would be for
>>>> Nate's option #1 and Andy's option #2, to give UnsafeRawBufferPointer a
>>>> Slice type that fulfills the requirement. It's the smallest change,
>>>> preserves the use of integer indices, and preserves what Andy stated as the
>>>> desired use case of making it easy for users to switch out code written for
>>>> [UInt8].
>>>> 
>>>> I'm not sure I fully understand yet why Dave finds the idea of Collection
>>>> conformance fishy, 
>>> 
>>> Because the memory can easily be already bound to another type than
>>> UInt8, and there's no obvious reason why UInt8 should be privileged as a
>>> type you can get out of a raw buffer without binding the memory.
>> 
>> I strongly disagree with that statement. The overwhelmingly common use
>> case for raw buffers is to view them as a sequence of UInt8 *without*
>> binding the type.  Generally, at the point that you're dealing with a
>> raw buffer it's impossible to (re)bind memory because you don't know
>> what type it holds. 
> 
> Oh, you can't just rebind to UInt8 because that's not defined as
> universally compatible with all data.  OK, sorry.
> 
>> The reason it's so important to have an UnsafeRawBufferPointer data
>> type is precisely so that users don't need mess about with binding
>> memory. It's easy to get that wrong even when it's possible.
>> 
>> The only reason that UInt8 is special is that when users create
>> temporary typed buffers for bytes (e.g. they sometimes want a growable
>> array or just don't want to bother with manual allocation) they always
>> use UInt8 as the element type.
>> 
>> That said, we could easily divide these concerns into two types as
>> you suggested. A raw buffer, which doesn't have any special UInt8
>> features, and a RawBytes collection that handles both buffer slicing
>> and UInt8 interoperability.
> 
> But, now that I think of it, that wouldn't really solve any problems,
> would it?


A new Collection type doesn't solve any practical problems. It does solve a
conceptual problem if you think that a raw buffer is not *inherently* a
collection of bytes. There is an elegance in separating the raw buffer
semantics from the byte collection semantics, but that elegance does
not simplify anything for users--it's just more abstraction to figure
out. Certainly, the most straightforward way to fix this is to simply
change raw buffer's slice type, so I'm inclined to favor that
approach. Creating a new collection type would involve
rethinking/redesigning some of the related APIs.

Also note that I'm leaning toward slice -> buffer conversion via an
unlabeled initializer because I think it's the most obvious with least
API surface.

I don't think we absolutely need a new proposal for this easy fix,
since it's not really introducing a new API. The additional
initializer merely allows code that used to work to be migrated via a
fixit.

Here's a quick summary. If there aren't any strong objections, I'll
post an ammendment to the original proposal along with a PR for more
formal review.

Proposed ammendment to SE-0138:
<https://github.com/apple/swift-evolution/blob/master/proposals/0138-unsaferawbufferpointer.md>

Fix: Change Unsafe${Mutable}RawBufferPointer's SubSequnce type

Original: Unsafe${Mutable}RawBufferPointer.SubSequence = Unsafe${Mutable}RawBufferPointer

Fixed: Unsafe${Mutable}RawBufferPointer.SubSequence = ${Mutable}RandomAccessSlice<Unsafe${Mutable}RawBufferPointer>

This is a source breaking bug fix that only applies to
post-3.0.1. It's extremely unlikely that any Swift 3 code would rely
on the Subsequence type, except for the simple use case of passing a
raw buffer subrange to an another raw buffer argument:

`takesRawBuffer(buffer[i..<j])`

A trivial fixit would insert an extra cast:

`takesRawBuffer(UnsafeRawBufferPointer(buffer[i..<j]))`

Add unlabeled initializers:

struct Unsafe${Mutable}RawBufferPointer {
  init(_ bytes: SubSequence)
}

struct UnsafeRawBufferPointer {
  init(_ bytes: RandomAccessSlice<UnsafeMutableRawBufferPointer>)
}

-Andy

PS: Thanks to Nate Cook and Kevin Ballard for raising this issue.


More information about the swift-evolution mailing list