Array is implicitly converted to UnsafePointer, but not UnsafeBufferPointer. Why?

I know that, in the grand scope of things, this is a non-problem. But I still find it curious, and a little odd.

func a(_ p: UnsafePointer<UInt32>) {
    print(p)
}

func b(_ p: UnsafeBufferPointer<UInt32>) {
    print(p)
}

let testArray: [UInt32] = [1, 2, 3, 4]

a(testArray)
// 0x0000600000abcdef

b(testArray)
// error: cannot convert value of type '[UInt32]' to expected argument type 'UnsafeBufferPointer<UInt32>'

The only difference between UnsafePointer and UnsafeBufferPointer is that the buffer pointer carries an element count, right? So why is Array allowed to implicitly cast to UnsafePointer but not UnsafeBufferPointer?

Yes, of course, one can (and should) use withUnsafeBufferPointer. I know. I also know that Array is not guaranteed to have contiguous storage, and withUnsafeBufferPointer might cause a copy (though I'm not sure if this ever actually happens in the standard library or Foundation). But it still seems weird to me. Why convert one kind of unsafe pointer implicitly, while the other (arguably safer) kind requires a pretty wordy function invocation?

I suspect the answer might be "C interop weirdness", but if there's more to it I'd be interested to know.

1 Like

That's pretty much it. All of the argument pointer conversions were intended for C interop, even though we didn't actually limit them to C functions, so they only support C types.

4 Likes

It's worth considering whether or not we might be able to restrict some of these C interop conversions to only apply to functions imported from C, to avoid confusion (and subtle sources of unsafety).

5 Likes

I think that's honestly not a bad idea, with the small caveat that you will probably break a lot of existing code :laughing:

4 Likes

I’ve always pushed back on that because it means functions can’t be migrated from C to Swift. Though I guess we could say @exposeToC or whatever re-enables the conversions, when it comes to that.

To migrate to Swift, you would "simply" add an explicit overload for the cases where the conversion actually makes sense (but ideally you would provide a better API in Swift anyway--simply migrating existing unsafe pointer functions to Swift means that you're replacing the implementation without benefiting from safer interfaces).

An explicit marker could also be an option, of course.

1 Like

Yeah, I was thinking specifically about the cases where you need to preserve the C ABI (or even just API), but it’s true you can always add overloads.

1 Like

I think it would be pretty reasonable to tie this up with a formalized cdecl as "this function has C language semantics (including both mangling/visibility for C and pointer conversions).”

5 Likes