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

Kevin Ballard kevin at sb.org
Wed Nov 30 16:39:05 CST 2016

This sounds like a sensible idea. But there is one behavioral change you
haven't addressed, which is that this changes how indexes work on the
slice. With all other slice types that come to mind, the slice shares
the same indexes as the base, e.g.

  let ary = Array(0..<10)

  print(ary[3]) // prints 3

  print(ary[2..<5][3]) // still prints 3

UnsafeBufferPointer is indexed using 0-based integers, so with your
proposal, slicing an UnsafeBufferPointer produces a value that uses
different indexes. We could solve this by adding a new field, but that
would break the expectation that startIndex is always zero. But we can't
just ignore this problem, because algorithms that are designed around
collections may assume that slices preserve indexes.

In addition, since you point out that UnsafeRawBufferPointer is already
its own subsequence, and that type also guarantees that startIndex is
always zero, it sounds like we already have an instance of this problem
in the stdlib, and so this needs to be addressed with
UnsafeRawBufferPointer as well.

-Kevin Ballard

On Wed, Nov 30, 2016, at 09:15 AM, Nate Cook via swift-evolution wrote:
> Hello all—

> This is a proposal for a fairly minor change in slicing behavior for
> unsafe buffers.
> Nate


> ------



> This proposal changes Swift's typed UnsafeBufferPointers to be their
> own slice type, like the UnsafeRawBufferPointer types. This is a minor
> change in the subscript API of UnsafeBufferPointer and
> UnsafeMutableBufferPointer, but constitutes a change to the standard
> library's ABI, as it can't be solved through type aliasing.
> Motivation

> The standard library has parallel pointer and buffer types for working
> with raw and typed memory. These types have broadly similar APIs that
> streamline working with pointers, as some kinds of memory manipulation
> involve moving back and forth between the two. One significant
> difference between the two groups of buffer types, however, is that
> while UnsafeRawBufferPointers are their own slice type,
> UnsafeBufferPointers use the default Slice type as a wrapper.
> Using a Slice wrapper is a needless addition when working with
> buffers—the wrapper is most useful when used to prevent copying of a
> collection's stored data, but since UnsafeBufferPointers aren't owners
> of the memory they reference, there is no copying performed when
> simply creating a new buffer over a subrange of the memory. Moreover,
> the overhead of a Slice wrapper around an UnsafeBufferPointer is
> almost certainly higher than another UnsafeBufferPointer. instance.
> The Slice wrapper makes using buffer pointers as parameters more
> cumbersome than necessary. To pass a slice of a buffer to a function
> taking a buffer, you need to create a new buffer manually:
> func _operateOnBuffer<T>(_ buffer: UnsafeMutableBufferPointer<T>) {
> // ... }

> let buffer: UnsafeMutableBufferPointer<Int> = ...
> _operateOnBuffer(buffer)            // okay
> _operateOnBuffer(buffer[..<16])    // error: type mismatch let
> subBuffer = UnsafeMutableBufferPointer(start: buffer, count: 16)
> _operateOnBuffer(subBuffer)         // okay
> The wrapper complicates subscript assignment, as well. Instead of
> using simple assignment to copy all the elements of one buffer into a
> memory range of another, you must either manually create a slice or
> subscript the source buffer with its full range:
> let biggerBuffer: UnsafeMutableBufferPointer<Int> = ... let
> smallerBuffer: UnsafeMutableBufferPointer<Int> = ...
> biggerBuffer[..<smallerBuffer.count] =
> smallerBuffer[..<smallerBuffer.count]
> Proposed solution

> The proposed solution is to switch the UnsafeBufferPointers to be
> their own slice type. This uses less overhead than the Slice type,
> which needs to store both the original buffer and a bounding range.
> The operations above are simpler with this change:

> _operateOnBuffer(buffer[..<16])        // subscripting okay
> // no need to subscript 'smallerBuffer'
> biggerBuffer[..<smallerBuffer.count] = smallerBuffer
> Detailed design

> The change follows the example of the raw buffer pointer types:

> struct UnsafeBufferPointer<Element> : Collection, ... {  // other
> declarations    subscript(bounds: Range<Int>) -> UnsafeBufferPointer {
> get {  // check bounds            return UnsafeMutableBufferPointer(
> start: self + bounds.lowerBound,  count: bounds.count) } } }

> struct UnsafeMutableBufferPointer<Element> : Collection, ... {  //
> other declarations    subscript(bounds: Range<Int>) ->
> UnsafeMutableBufferPointer {  get {  // check bounds            return
> UnsafeMutableBufferPointer(  start: self + bounds.lowerBound,  count:
> bounds.count) }  set {  // check bounds
> _writeBackMutableSlice(&self, bounds: bounds, slice: newValue) } } }
> Impact on existing code

> Any existing code that works with slices of UnsafeMutableBufferPointer
> and specifies the Slice type explicitly will need to change that
> specification. This isn't a terribly common thing to do (I can't find
> any in the standard library or test suite), so the impact of the
> change should be minor.


> _________________________________________________

> swift-evolution mailing list

> swift-evolution at swift.org

> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20161130/2289f971/attachment.html>

More information about the swift-evolution mailing list