[swift-evolution] [Pitch] Don't require & for UnsafeRawPointer

Andrew Trick atrick at apple.com
Wed May 17 17:10:11 CDT 2017


Thanks for refusing to let your pitch die. Something should eventually be done here and it's good to get feedback. The only reason to bring this up in Swift 4 is if we decide to outlaw some code pattern that's already in use. If this ends up just an additive convenience request, than it can be a bug report for now and come back in Swift 5. There have been a couple +1's already but I don't know whether it's a serious problem or just an annoyance. 

Side note: don’t worry, simply declaring everything ‘var’ and passing them ‘inout’ shouldn’t break exclusive memory access. If you pass ‘inout’ to a non-mutable pointer, it’s just considered a “read” access.

I'll speculate a bit and make some suggestions...

> On Apr 20, 2017, at 12:10 PM, Anders Kierulf via swift-evolution <swift-evolution at swift.org> wrote:
> 
> Summary: Currently, only mutable values can be passed to UnsafeRawPointer, except for a special case for arrays. That special case should be generalized, allowing any values to be passed to UnsafeRawPointer without using &.
> 
> The following code shows the inconsistency in passing values to UnsafeRawPointer:
> 
> var varArray = [Int](repeating: 0, count: 6)
> var varTuple = (0, 0, 0, 0, 0, 0)
> 
> let letArray = [Int](repeating: 0, count: 6)
> let letTuple = (0, 0, 0, 0, 0, 0)
> 
> func get(_ pointer: UnsafeRawPointer, at index: Int) -> Int {
>    let a = pointer.bindMemory(to: Int.self, capacity: 6)
>    return a[index]
> }
> 
> // Array can be passed directly, automatically takes address.
> _ = get(varArray, at: 2) // okay
> _ = get(letArray, at: 2) // okay

This works out because a special implicit conversion is doing the work for us. I think the special case is all about lightweight C interop with Swift Array.

Let's not change this.

> // When explicitly taking address, can only pass mutable variables.
> _ = get(&varArray, at: 2) // okay, but seems inconsistent
> _ = get(&letArray, at: 2) // fails: & not allowed

I'll speculate that the type checker has a special case for `&varArray` for two reasons:

1. To prevent users from accidentally exposing the Array struct rather than its storage as a C pointer. That would be confusing to debug.

2. We need a mutable analog to the non-inout immutable case above, for lightweight C interop.

We allow implicit UnsafeMutablePointer to UnsafePointer, so, I don't think there's anything inconsistent about `&varArray` here.

I also think it's fine that we don't allow `&letArray`.

Let's not change this.

> // Passing tuple instead of array fails.
> _ = get(varTuple, at: 2) // fails: wrong type
> _ = get(letTuple, at: 2) // fails: wrong type

Refusing to compile these cases would probably seem normal if we didn't support the first non-inout array case.

I think we should allow both cases as a new, additive type system feature.

> // Adding & to pass a tuple only works for mutable values. Having to
> // pass the address using & means that any methods calling this must
> // be mutating, even though they don't mutate.
> _ = get(&varTuple, at: 2) // okay, but forces mutating
> _ = get(&letTuple, at: 2) // fails: cannot pass immutable value

Well, the `&varTuple` case only works by accident for raw pointers, but typed pointers will have the wrong type!

See [SR-3590] Implicitly convert &Tuple to UnsafePointer<Tuple.Element>.

The only reason I haven't pushed for this in Swift 4, along with several other important usability issues with UnsafePointer, is that (I thought) it's additive and I haven't wanted to compete for review bandwidth in this release cycle.

However, come to think of it, this will break some very unlikely code:

func foo(_ p: UnsafeMutablePointer<(Int, Int, Int)>) {
  p[0].0 = 42
}

var a = (0, 1, 2)
foo(&a)
print(a)

I think that's fair if migration is provided.

> Passing a value to an UnsafeRawPointer parameter should not require use of &, as that forces all code calling it to be mutating. Having a special case for array also doesn't make sense, as the idea of UnsafeRawPointer is that we're interpreting that memory content as whatever we want. Logged as SR-4649.

Splitting hairs: the array case is really a special case and it does make sense. I'm *also* proposing a similar special case for homogeneous tuples.

> Proposal:
> - Never require & when passing value to UnsafeRawPointer.
> - Always require & when passing value to UnsafeMutableRawPointer.

+1 to your basic proposal. But I think each type system change needs to be more clearly spelled out. Probably with a separate bug for each.

- Support non-inout homogeneous tuple argument conversion to UnsafePointer<Element>.
(additive)

- Support non-homgenous non-inout tuple argument conversion to UnsafeRawPointer.
(additive)

- Support inout homogeneous tuple argument conversion to UnsafeMutablePointer<Element>.
See [SR-3590] Implicitly convert &Tuple to UnsafePointer<Tuple.Element>.
(source breaking in ridiculous cases)

- [SR-1956] `withUnsafePointer` shouldn't take its argument as `inout`
(additive... we don't need to ban the inout syntax)

The problem I have with [SR-4649] "Don't require & to pass value to UnsafeRawPointer", is that it seeks to create inconsistency between raw and non-raw pointers. That might make sense for non-homogenous tupes, but otherwise I don't think it's desirable. 

> 
> (Fixed size arrays are still needed, but with this tweak, workarounds can be made to work.)

Yes, they very much are needed to get people out of unsafe territory.

-Andy




More information about the swift-evolution mailing list