[swift-users] Why does the withUnsafeMutableBufferPointer closure take an inout parameter?

Braden Scothern bradenscothern at gmail.com
Sat Oct 14 11:28:47 CDT 2017


There is a lot of value in these being mutating functions when working with C APIs that need a buffer for it to write to.

If you have a collection and ensure it had allocated it's memory, you can then have the C API write directly into the collection's storage avoiding extra copy operations.

I personally take advantage of this a lot. Because I'm wrapping a C library in Swift as part of my work.

If this was changed to only non mutating functions, it still wouldn't be able to guarantee that the buffer isn't mutating the collection without always doing a deep copy. It is very easy to go from an UnsafeBufferPointer -> UnsafeMutableBufferPointer and have people change things.

> On Oct 14, 2017, at 7:11 AM, Martin R via swift-users <swift-users at swift.org> wrote:
> 
> 
>> On 14. Oct 2017, at 13:15, Geordie Jay via swift-users <swift-users at swift.org> wrote:
>> 
>> Chris Lattner <clattner at nondot.org> schrieb am Sa. 14. Okt. 2017 um 05:18:
>> 
>>> On Oct 13, 2017, at 7:40 PM, Andrew Trick via swift-users <swift-users at swift.org> wrote:
>>> 
>>> 
>>> 
>>>> On Oct 12, 2017, at 3:52 AM, Geordie Jay via swift-users <swift-users at swift.org> wrote:
>>>> 
>>>> 
>>>> Guillaume Lessard via swift-users <swift-users at swift.org> schrieb am Mi. 11. Okt. 2017 um 23:49:
>>>> A lot of the MutableCollection implementation is in protocol extensions (swapAt, for example.)
>>>> 
>>>> Should an additional version be written just for the Unsafe*BufferPointer types?
>>>> 
>>>> Makes sense to me, given the examples above. It doesn’t seem to be a high priority task though, and one one suited to a contributor pull request rather than taking resources away from the core team.
>>>> 
>>>> Would this kind of change need to go through swift evolution or is it a “no brainer”?
>>>> 
>>>> Geordie
>>> 
>>> I’ll just point out that it’s already the case that methods defined in Unsafe*BufferPointer that write to memory are “nonmutating”. So I think it’s both a “no brainer” and needs to go through evolution.
>> 
>> I’m not familiar with the specifics of this “proposal” but if it really is just moving something obvious from being a mutating member to a nonmutating member, then I’m sure the core team can find a fast-path way to accept it without a formal review period.
>> 
>> I’m not 100% sure either tbh, although I can imagine putting in a PR for it once i understand it.
>> 
>> The issue with changing .swapTo (et al.?) to nonmutating is that semantically it really is a mutation. But pointers as collections have different mutation semantics to other collections: Mutating an Array’s storage is the same as mutating its value, whereas a pointer’s value is its address, not its storage.
>> 
>> Making the Unsafe*Pointer MutableCollection methods themselves nonmutating probably wouldn’t be a source-breaking change (it’d just lower the “bar of entry” to include let constant instances too). I imagine this is noncontroversial.
>> 
>> The original question though was about why .withUnsafeMutableBufferPointer takes a closure whose first argument is an *inout* UnsafeMutableBufferPointer, implying that its base address could legally be mutated. This was probably done to ease use of MutableCollection methods given the var parameter. That would no longer be necessary given the first change.
>> 
>> But removing the inout attribute would be source-breaking. And maybe people really are moving around the array storage’s base address? This seems like a very bad idea to me but it’s currently part of that API contract. In any case this change would need to go through evolution, right?
> 
> At present, the withUnsafeMutableBufferPointer() method verifies that the base address has not been modified upon return from the closure: 
> 
>    https://github.com/apple/swift/blob/master/stdlib/public/core/Arrays.swift.gyb#L1750
> 
>    // Put the working array back before returning.
>    defer {
>      _precondition(
>        inoutBufferPointer.baseAddress == pointer &&
>        inoutBufferPointer.count == count,
>        "${Self} withUnsafeMutableBufferPointer: replacing the buffer is not allowed")
> 
>      (work, self) = (self, work)
>    }
> 
> That check would become obsolete if the buffer pointer is a constant.
> 
> As far as I can see, MutableCollection defines four mutating methods: swapAt(), sort(), reverse(), and partition(). Implementing those as non-mutating methods of UnsafeMutableBufferPointer seems natural to me.
> 
> In addition, it would allow to make the closure parameter in withUnsafeMutableBufferPointer() a constant. Mutating that pointer in the closure is not allowed anyway. 
> 
> Btw, I came up with these questions because I noticed that this compiles:
> 
>    func foo(bufptr: UnsafeMutableBufferPointer<Int>) { }
>    var a = [1, 2, 3, 4, 5]
>    a.withUnsafeMutableBufferPointer { foo(bufptr: $0) }
> 
> but this does not compile:
> 
>    a.withUnsafeMutableBufferPointer(foo)
>    // error: cannot convert value of type '(UnsafeMutableBufferPointer<Int>) -> ()'
>    // to expected argument type '(inout UnsafeMutableBufferPointer<Int>) -> _'
> 
> which looks strange at first sight. With a constant closure parameter, the second version would compile as well.
> 
> Any advice how to proceed with this "issue" would be appreciated: File a bug report? Post on swift-evolution? Make a proposal? ... However, I am not familiar with those evolution procedures. I won't mind to sidestep if someone else want to jump in. 
> 
> Martin
> 
>> 
>> - Geordie
>> 
>> 
>> -Chris
>> 
>> 
>> _______________________________________________
>> swift-users mailing list
>> swift-users at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-users
> 
> _______________________________________________
> swift-users mailing list
> swift-users at swift.org
> https://lists.swift.org/mailman/listinfo/swift-users


More information about the swift-users mailing list