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

Nate Cook natecook at gmail.com
Wed Nov 30 11:15:35 CST 2016


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.
 <https://gist.github.com/natecook1000/a239f4db2704db9c5d54d540c0a09499#motivation>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[0..<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[0..<smallerBuffer.count] = 
    smallerBuffer[0..<smallerBuffer.count]
 <https://gist.github.com/natecook1000/a239f4db2704db9c5d54d540c0a09499#proposed-solution>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[0..<16])        // subscripting okay

// no need to subscript 'smallerBuffer'
biggerBuffer[0..<smallerBuffer.count] = smallerBuffer 
 <https://gist.github.com/natecook1000/a239f4db2704db9c5d54d540c0a09499#detailed-design>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)
        }
    }
}
 <https://gist.github.com/natecook1000/a239f4db2704db9c5d54d540c0a09499#impact-on-existing-code>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.


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


More information about the swift-evolution mailing list