[swift-evolution] Proposal: One-past-end array pointers and convertibility of nil for interaction with C APIs

Árpád Goretity arpad.goretity at gmail.com
Thu Dec 24 14:48:46 CST 2015


Thanks for your answer!

> I think the thing to do is to make two calls:

Eeeek, that's exactly what I wanted to avoid (which I actually think is the
worst possible solution).

> The inout-to-pointer is only available to function argument expressions;
it's not even considered in a ternary expression here.

Yes, I realized that (in fact I think the diagnostic makes this quite
clear). I understand *why* it does not currently work, I'm merely
suggesting that I think it *should* work. (It might be too hard to
implement, just my opinion.)

> In general, we can't support fully first-class pointers into managed
Swift entities like Array and properties, without breaking the
encapsulation of those abstractions.

Yes, that's kind of obvious – what I am actually trying to suggest is that
unsafe is already unsafe, so it might be OK to "break the encapsulation",
for example by returning a pointer to the internal buffer without
introducing a temporary.

However, if you agree about sharply distinguishing Swift inout and unsafe +
C address of (possibly in a way that my examples would work with the new
unsafe address-of operator), I'm happy to narrow down my proposal to that
specific case.


On Thu, Dec 24, 2015 at 9:43 PM, Árpád Goretity <arpad.goretity at gmail.com>
wrote:

> Thanks for your answer!
>
> > I think the thing to do is to make two calls:
>
> Eeeek, that's exactly which I wanted to avoid (and which I think is the
> worst possible solution).
>
> On Thu, Dec 24, 2015 at 5:26 PM, Joe Groff <jgroff at apple.com> wrote:
>
>>
>> On Dec 23, 2015, at 5:35 PM, Árpád Goretity via swift-evolution <
>> swift-evolution at swift.org> wrote:
>>
>> Hi everyone,
>>
>> I was recently trying to use a C API (LLVM for the record) that required
>> passing an array to a function in the form of a pointer and a size. I
>> couldn't find a straightforward way to pass a null pointer to the function
>> in question conditionally (when the array is empty), since the following –
>> simplified – code doesn't currently typecheck:
>>
>>     // C function with signature: void foo(T *ptr, unsigned size)
>>     // imported into Swift as: (UnsafeMutablePointer<T>, UInt32) -> ()
>>     var arr: [T] = []
>>     foo(arr.count > 0 ? &arr[0] : nil, UInt32(arr.count))
>>
>> The error is: result values in '? :' expression have mismatching types
>> 'inout T' and '_'
>>
>>
>> The diagnostic here sucks. The inout-to-pointer is only available to
>> function argument expressions; it's not even considered in a ternary
>> expression here, so the type checker can't find any way to match 'nil' and
>> an inout.
>>
>> Since the inout operator (&) can only be used in function call arguments
>> (so it's not exactly C's address-of), I believe that there's no easy way of
>> elegantly passing a null pointer when the array is empty. (Yes, I could
>> write two almost-identical calls, but meh…) And even if there is one (and
>> I'm just missing it), the fact that the above code does not work seems
>> inconsistent to me.
>>
>> I also realized that this specific issue generalizes to the (in)ability
>> of passing one-past-end pointers – which would be equally valid and even
>> more convenient in the above case, as the callee does not dereference the
>> passed pointer when the count is 0, but in general, it can be applied to
>> functions accepting [begin, end + 1) ranges.
>>
>> The problem here is that a one-past-end pointer does not reside at a
>> valid index (pretty much by definition), so bounds checking kicks in and
>> kills the program.
>>
>>
>> Past-the-end indices are valid pointers (and valid in Swift collections
>> in general). That's not the problem. `&arr[0]` fails because it's providing
>> a temporary buffer connected only to the *element* &arr[0], rather than a
>> buffer representing the entire array. This won't do what you expect for any
>> Swift array, even if it's non-empty.
>>
>> In general, we can't support fully first-class pointers into managed
>> Swift entities like Array and properties, without breaking the
>> encapsulation of those abstractions. We can provide scoped operations like
>> `withUnsafePointer` that give you a pointer to a possibly-temporary buffer
>> that represents the value of that array or value for the duration of a
>> block. When you say `CFunctionThatTakesPointer(&a)`, Swift's really
>> wrapping that call in the equivalent of `withUnsafeMutableBufferPointer` on
>> your behalf. You can see how that would be problematic if the wrapping
>> needs to be conditional, such as if it appeared in a ternary or &&/||
>> expression. I think the thing to do is to make two calls:
>>
>> if arr.empty {
>>   foo(nil, 0)
>> } else {
>>   foo(&arr, arr.count)
>> }
>>
>> since preparing the buffer for the pointer itself isn't necessarily free,
>> and you'd want to avoid that work if you don't need it.
>>
>> It might be OK to have the pointer produced for an empty array be null to
>> begin with, which would avoid the need for this conditional at all. In most
>> cases, you can't safely dereference a pointer to nothing anyway. I'm also
>> sympathetic to the idea of disconnecting "address-of" and "inout", since it
>> often leads to confusion like this.
>>
>> -Joe
>>
>> My proposed solutions:
>>
>>  – Extend type inference for unsafe pointers and nil, so that when a
>> value is passed by address to a function, it's not only the result of an
>> &-expression that has its type inferred to be (or implicitly converted to)
>> Unsafe[Mutable]Pointer, but if there's a nil somewhere around, such as the
>> one in the example above, it gets promoted to that type too, just like NULL
>> in C or nullptr in C++.
>>
>>  – Stop overloading the inout '&' operator and using it for C-style
>> address-of operations. I could imagine a similar, but distinct operator or
>> even a library function (something along the lines of unsafeAddressOf) that
>> specifically yields the physical address of its operand as an unsafe C
>> pointer, and which is thus first-class in the sense that it may be used
>> anywhere other expressions may be, not just as immediate call arguments.
>>
>>  – Make array bounds checking more lenient when passing pointers to array
>> elements into C functions. Bounds checking should, in these cases, allow
>> indexing the one-past-end element of an array if (and only if) it is the
>> argument of the address-of operator.
>>
>> Comments and questions are welcome (you might need clarification, as it's
>> 2:35 AM here when I'm writing this…)
>>
>> Cheers,
>>
>> --
>> Author of the Sparkling language
>> http://h2co3.org/
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
>>
>
>
> --
> Author of the Sparkling language
> http://h2co3.org/
>
>


-- 
Author of the Sparkling language
http://h2co3.org/
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151224/5b6d6ae4/attachment.html>


More information about the swift-evolution mailing list