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.
···
On Dec 9, 2016, at 11:50 AM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
on Fri Dec 09 2016, Andrew Trick <swift-evolution@swift.org> wrote:
On Dec 9, 2016, at 10:27 AM, Dave Abrahams via swift-evolution >> <swift-evolution@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@swift.org> wrote:
On Dec 8, 2016, at 4:35 PM, Jordan Rose via swift-evolution < >>>>> swift-evolution@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?