This proposal changes Swift's typed UnsafeBufferPointer
types 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.
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 different between the two groups of buffer types, however, is that while UnsafeRawBufferPointer
s are their own slice type, typed UnsafeBufferPointer
s use the default Slice
type.
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 UnsafeBufferPointer
s aren't owners of the memory they reference, there is no copying performed in 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
.
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]
The proposed solution is to switch the UnsafeBufferPointer
s 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
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)
}
}
}
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.