[swift-evolution] Shouldn't ".withUnsafeBufferPointer" and ".withUnsafeMutableBufferPointer" be parts of protocols?

Daryle Walker darylew at mac.com
Fri Jan 27 17:40:24 CST 2017


I was perusing the library for array ideas, and noticed that several types had methods in common, but without a grouping protocol. Shouldn’t that be fixed?

(Oh, if multiple protocols have an associated-type with the same name, is that a problem? Is it a problem if they resolve differently for each protocol attached to a given type? I’m asking because these protocols reuse Sequence’s Element for their own purpose.)


Formal Protocol for Contiguous Storage Visitation
Proposal: SE-NNNN <file:///Users/daryle/Documents/NNNN-filename.md>
Authors: Daryle Walker <https://github.com/CTMacUser>
Review Manager: TBD
Status: Awaiting review
During the review process, add the following fields as needed:

Decision Notes: Rationale <https://lists.swift.org/pipermail/swift-evolution/>, Additional Commentary <https://lists.swift.org/pipermail/swift-evolution/>
Bugs: SR-NNNN <https://bugs.swift.org/browse/SR-NNNN>, SR-MMMM <https://bugs.swift.org/browse/SR-MMMM>
Previous Revision: 1 <https://github.com/apple/swift-evolution/blob/...commit-ID.../proposals/NNNN-filename.md>
Previous Proposal: SE-XXXX <file:///Users/daryle/Documents/XXXX-filename.md>
Introduction
The standard library types Array, ArraySlice, and ContiguousArray have an interface for visiting their elements as a contiguous block of memory (arranging said elements to that configuration first if necessary). These methods are all the same, but not under a common protocol (i.e. seeming to match by "coincidence").

This proposal seeks to correct that with two new protocols that these types will implement.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/>
Motivation
Just adding these protocols for consistency is relatively minor, but they may be used for other types. Particularly, they would be needed if fixed-sized arrays are added to the language.

Proposed solution
The library array types will follow the MutableContiguousBlockprotocol, which inherits from the ContiguousBlock protocol.

extension Array: MutableContiguousBlock {
}

extension ArraySlice: MutableContiguousBlock {
}

extension ContiguousArray: MutableContiguousBlock {
}
For example, a repackaging of the library's example code:

func change<T: MutableContiguousBlock>(object: inout T) -> T.Element where T.Element: Integer {
    let sum = object.withUnsafeBufferPointer { (buffer) -> T.Element in
        var result: T.Element = 0
        for i in stride(from: buffer.startIndex, to: buffer.endIndex, by: 2) {
            result += buffer[i]
        }
        return result
    }
    object.withUnsafeMutableBufferPointer { buffer in
        for j in stride(from: buffer.startIndex, to: buffer.endIndex - 1, by: 2) {
            swap(&buffer[j], &buffer[j + 1])
        }
    }
    return sum
}

var numbers = [1, 2, 3, 4, 5]
print(change(object: &numbers))  // 9
print(numbers)                   // [2, 1, 4, 3, 5]
Detailed design
/**
    Visitation protocol of the receiver's contiguous storage of immutable elements.
 */
protocol ContiguousBlock {

    /// Inferred alias to the element type to visit
    associatedtype Element

    /**
        Calls a closure with a pointer to the receiver's contiguous storage. If no such storage exists, it is first created.

        Often, the optimizer can eliminate bounds checks within an array algorithm, but when that fails, invoking the same algorithm on the buffer pointer passed into your closure lets you trade safety for speed.

        The following example shows how you can iterate over the contents of the buffer pointer:

            let numbers = [1, 2, 3, 4, 5]
            let sum = numbers.withUnsafeBufferPointer { buffer -> Int in
                var result = 0
                for i in stride(from: buffer.startIndex, to: buffer.endIndex, by: 2) {
                    result += buffer[i]
                }
                return result
            }
            // 'sum' == 9

        - Parameter body: A closure with an `UnsafeBufferPointer` parameter that points to the contiguous storage for the receiver. If `body` has a return value, it is used as the return value for this method. The pointer argument is valid only for the duration of the closure's execution.

        - Returns: The return value of the `body` closure parameter, if any.

        - SeeAlso: Swift.UnsafeBufferPointer
     */
    func withUnsafeBufferPointer<R>(_ body: (UnsafeBufferPointer<Element>) throws -> R) rethrows -> R

}

/**
    Visitation protocol of the receiver's contiguous storage of elements, allowing mutation.
 */
protocol MutableContiguousBlock: ContiguousBlock {

    /**
        Calls the given closure with a pointer to the receiver's mutable contiguous storage. If no such storage exists, it is first created.

        Often, the optimizer can eliminate bounds checks within an array algorithm, but when that fails, invoking the same algorithm on the buffer pointer passed into your closure lets you trade safety for speed.

        The following example shows modifying the contents of the `UnsafeMutableBufferPointer` argument to `body` alters the contents of the receiver:

            var numbers = [1, 2, 3, 4, 5]
            numbers.withUnsafeMutableBufferPointer { buffer in
                for i in stride(from: buffer.startIndex, to: buffer.endIndex - 1, by: 2) {
                    swap(&buffer[i], &buffer[i + 1])
                }
            }
            print(numbers)
            // Prints "[2, 1, 4, 3, 5]"

        - Warning: Do not rely on anything about `self` (the receiver of this method) during the execution of the `body` closure: It may not appear to have its correct value. Instead, use only the `UnsafeMutableBufferPointer` argument to `body`.

        - Parameter body: A closure with an `UnsafeMutableBufferPointer` parameter that points to the contiguous storage for the receiver. If `body` has a return value, it is used as the return value for this method. The pointer argument is valid only for the duration of the closure's execution.

        - Returns: The return value of the `body` closure parameter, if any.

        - SeeAlso: withUnsafeBufferPointer, Swift.UnsafeMutableBufferPointer
     */
    mutating func withUnsafeMutableBufferPointer<R>(_ body: (inout UnsafeMutableBufferPointer<Element>) throws -> R) rethrows -> R

}
I don't know how the 3 library types implement this methods, but we know it can be done. If added as an implicit interface to built-in fixed-sized arrays, the implementers can use similar techniques or compiler magic. I don't know the difficulty to adapt these interfaces with third-party code.

Source compatibility
The changes are strictly additive, and at the library level, so there should be no impact to existing code.

Effect on ABI stability
These changes should not affect ABI stability.

Effect on API resilience
As just stated, the addition of two protocol names to the API shouldn't affect the ABI.

Alternatives considered
The main alternative is to do nothing. If fixed-sized arrays are later added, they could have the same methods too, but there would still be no common link.

Another alternative would make MutableContiguousBlock not inherit from ContiguousBlock, meaning the former would need to define an Element associated type. But don't see a need for a protocol with a read-write method without its read-only counterpart available.


— 
Daryle Walker
Mac, Internet, and Video Game Junkie
darylew AT mac DOT com 

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


More information about the swift-evolution mailing list