`withUnsafePointer` and `withUnsafeBytes` for immutable arguments

The global withUnsafePointer and withUnsafeBytes functions currently require their pointee argument to be inout. Because these functions restrict what you're allowed to do with the pointer your closure receives—specifically, you can't use the pointer after the closure returns, and you can't mutate through it—there's no formal reason they couldn't also work on immutable arguments with those same restrictions. In effect, the immutable-argument versions can be written in terms of the inout-argument ones:

func withUnsafeBytes<T, Result>(of value: T, _ body: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result {
  var value2 = value
  return withUnsafeBytes(of: &value2, body)
}

like many people do manually today, though the copy shouldn't really be necessary, particularly now with the default +0 argument convention. Here's a PR that implements variants of these functions for immutable values: stdlib: Add withUnsafeBytes(of:) and withUnsafePointer(to:) for immutable arguments. by jckarter · Pull Request #15608 · apple/swift · GitHub Does anyone see any problems with proposing adding these forms?

7 Likes

Potential downside: previously, withUnsafeBytes(&self.storedProp) would give you the same buffer pointer each time and withUnsafeBytes(self.storedProp) would fail. With this change, the latter will now succeed but always do a load if the closure mentions self in any way.

I think the upside probably outweighs this downside, though.

You're not formally guaranteed to get the same or different pointers in either situation. It's possible in theory with the +0 convention for the immutable forms to produce the same pointer for stored properties in similar situations as we do with the inout forms.

That's true, the direct inout-to-pointer conversion (&self.foo for an argument with pointer type) is the only place where that's really guaranteed.

It is true on the other hand that, without move-only types and/or a "no implicit copies" language mode, that the immutable argument value is more likely to introduce a temporary copy if the compiler can't guarantee the immutability of the value's storage, whereas &prop more forcefully asserts exclusive access to prop. We don't have to take the inout form away, though.

4 Likes

I drafted a formal proposal here:

https://github.com/apple/swift-evolution/pull/822

1 Like

+1 from me, otherwise I have to make a copy of the value first. Making a copy of the value leads to reliance on an optimization to elide the copy that doesn't/might-not happen (SR-4581). I'd rather rely on language behavior than hope for optimizations.

1 Like

The language behavior says that there's a copy, and this proposal doesn't change that.

The argument is at least passed at +0, allowing it to share representation with the caller-side argument. When we flesh out the borrow model we can more formally guarantee when copies can be avoided.

2 Likes