[swift-evolution] [Draft] UnsafeRawPointer API

L. Mihalkovic laurent.mihalkovic at gmail.com
Tue Jun 28 00:30:53 CDT 2016



Regards
LM
(From mobile)

> On Jun 28, 2016, at 12:25 AM, Dave Abrahams <dabrahams at apple.com> wrote:
> 
> 
>> on Mon Jun 27 2016, "L. Mihalkovic" <laurent.mihalkovic-AT-gmail.com> wrote:
>> 
>> Regards
>> (From mobile)
>> 
>>> On Jun 27, 2016, at 8:39 AM, Dave Abrahams <dabrahams at apple.com> wrote:
>>> 
>>> 
>>> on Fri Jun 24 2016, Andrew Trick <atrick-AT-apple.com> wrote:
>>> 
>>>>> On Jun 24, 2016, at 11:22 AM, Andrew Trick via swift-evolution
>>>>> <swift-evolution at swift.org> wrote:
>>>>> 
>>>>>> On Jun 24, 2016, at 11:17 AM, L. Mihalkovic
>>>>>> <laurent.mihalkovic at gmail.com
>>>>>> <mailto:laurent.mihalkovic at gmail.com>> wrote:
>>>>>> 
>>>>>> I like the watch-what-you-wish-for warning of unsafeCast.
>>>>> 
>>>>> I’ll try porting stdlib to the “UnsafeRawPointer.unsafeCast(to:
>>>>> T.Type)” syntax and see how bad it is.
>>>> 
>>>> I don't think there's a clear winner here. Let me enumerate some
>>>> options.
>>>> 
>>>> Option (1) UnsafePointer<T>(cast: UnsafeRawPointer)
>>> 
>>> The problem with this one is that T can be deduced based on type
>>> context.  I think we ought to move away from that for operations like
>>> this one.
>>> 
>>>> Option (2) UnsafePointer<T>(_: UnsafeRawPointer, to: T.self)
>>> 
>>> I think you mean T.Type, not T.self, because this looks like a declaration.
>>> 
>>> To evaluate, you have to look at the use-site:
>>> 
>>>   let p = UnsafePointer(r, to: Int.self)
>>> 
>>> I don't find “to” to be descriptive enough.  Maybe
>> 
>> toType
>> 
>>> 
>>>   let p = UnsafePointer(r, pointee: Int.self)
>> 
>> I find pointee a total aberation :)
>> 
>>> 
>>> is better.  But I hate that the language doesn't give us a way to say
>>> “don't deduce generic parameters here.”  This is the only syntax that
>>> feels right, IMO:
>>> 
>>>   let p = UnsafePointer<Int>(r)
>>> 
>>>> Option (3) UnsafeRawPointer.unsafeCast<T>(to: T.Type) ->
>>>> UnsafePointer<T>
>>> 
>>>   r.unsafeCast(to: Int.self)
>>> 
>>> I don't see adding “unsafe” to the name of the operation as adding
>>> anything.  It isn't any more unsafe than other UnsafeRawPointer
>>> operations.  
>> 
>> It is unsafe in the sense that there are no guarantees that it is a
>> sensible thing to do.
> 
> Just like most of the other operations on UnsafeRawPointer, which is my
> point.
> 
>> I guess that means it is more 'noguaranteeexplicitorimpliedapplied' in
>> the sense that it will like mechanically work, even if it produce an
>> aberation as a result
>> 
>>> Also, it reads like we're casting the raw pointer to an
>>> Int, rather than to an UnsafePointer<Int>.
>> 
>> Really good one... But then instead of 'to' or 'pointee', something
>> along the lines of 'wrappedType', which lookes a little less
>> balerina-ish than pointee.....
> 
> A pointer does not wrap its pointee.

Mix a->b & from b
  let p = UnsafePointer(r, pointee: Int.self)
  let p = UnsafePointer(r, wrappedType: Int.self)

Purely a->b 
  let p = UnsafePointer(r, toObjectOfType: Int.self)
  let p = UnsafePointer(r, targetType: Int.self)
  let p = UnsafePointer(r, to: Int.self)
  let p = UnsafePointer(r, toType: Int.self)  
  let p = UnsafePointer(r, destinationType: Int.self)

You are of course absolutely right about wrappedType :)

I just think (for no other excuse than years of c) that when i think about a pointer i think of it directionally. And my unease comes from pointee suddenly shifting (for me) the view point: i suddenly have to shift to seeing the world from the other side. All the words i was trying (with the exception of wrappedType which i kept in the first group) share the  FromSourceToDestination connotation that i implicitly associate with pointers. But this is just me and this is purely subjective. Maybe it is time i break this mental model... I still find pointee a little too Bolshoi for my taste (nothing i wont get used to though).

>>> Also, how do you get an
>>> UnsafeMutablePointer?
>>> 
>>>> Option (4) unsafeCast(rawPointer: UnsafeRawPointer, to: T.self) ->
>>>> UnsafePointer<T>
>>> 
>>> This one won't read correctly for the same reasons as #3.
>>> 
>>>   r.cast(to: UnsafePointer<Int>.self)
>>> 
>>> works better for me than any of the alternatives given our inability to
>>> get the One True Syntax.
>>> 
>>>> ---
>>>> Option (3) is the most explicit and searchable, and forces
>>>> UnsafeRawPointer to be spelled out in the conversion (unless you
>>>> already have a raw pointer).
>>> 
>>> Huh?  I'm confused here.  What you wrote looks like it's intended to be
>>> a regular method, in which case of course invoking it would require a raw
>>> pointer and wouldn't force you to write UnsafeRawPointer out anywhere.
>>> 
>>> The only way it could force you to write UnsafeRawPointer would be if it
>>> was a static method, but in that case it has too few arguments.
>>> 
>>>> I like this because conceptually, you need to cast to a raw pointer
>>>> before casting to a new pointee type, and casting a raw pointer to a
>>>> typed pointer carries important semantics beyond simply converting to
>>>> a typed pointer. The main problem with Option (3) is that optional raw
>>>> pointers can't be converted naturally (without using `map`).
>>> 
>>> r ?? someExpressionUsing(r!)
>>> 
>>> best I can do.
>>> 
>>>> Another thing I'm a little nervous about is confusing a cast of the
>>>> pointer value with a cast of the pointee type:
>>>> 
>>>> `unsafeBitCast(rawPtr, to: Int.self)`
>>>> 
>>>> is very different from
>>>> 
>>>> `rawPtr.unsafeCast(to: Int.self)`
>>>> 
>>>> Does this need to be clarified?
>>> 
>>> Yes!
>>> 
>>>> If so, we can go back to the `toPointee` label that I proposed
>>>> earlier.
>>>> 
>>>> With that in mind, Option(4) is starting to look pretty good.
>>>> 
>>>> Examples:
>>>> 
>>>> ---
>>>> Case 1: casting a raw pointer as an argument
>>> 
>>> Use sites! (yay)...
>>> 
>>>> func foo(_: UnsafePointer<A>)
>>>> 
>>>> let rawPtr = UnsafeRawPointer(...)
>>>> 
>>>> (1) foo(UnsafePointer(cast: rawPtr))
>>>> 
>>>> (2) foo(UnsafePointer(rawPtr, to: A.self))
>>>> 
>>>> (3) foo(rawPtr.unsafeCast(to: A.self))
>>>> 
>>>> (4) foo(unsafeCast(rawPointer: rawPtr, to: A.self))
>>> 
>>> 
>>> foo(rawPtr.cast(to: UnsafePointer<A>.self))
>>> 
>>>> ---
>>>> Case 2: "recasting" a typed pointer argument
>>>> 
>>>> Note that typed pointer arguments are implicitly cast to raw pointer
>>>> arguments, so the conversion from PtrB to raw is implicit.
>>>> 
>>>> func foo(_: UnsafePointer<A>)
>>>> 
>>>> let ptrB = UnsafePointer<B>(...)
>>>> 
>>>> (1) foo(UnsafePointer(cast: ptrB))
>>>> 
>>>> (2) foo(UnsafePointer(ptrB, to: A.self))
>>>> 
>>>> (3) foo(UnsafeRawPointer(ptrB).unsafeCast(to: A.self))
>>>> 
>>>> (4) foo(unsafeCast(rawPointer: ptrB, to: A.self))
>>> 
>>> foo(UnsafeRawPointer(ptrB).cast(to: UnsafePointer<A>.self))
>>> 
>>> I don't believe in making these “double-hops” concise.
>>> 
>>>> ---
>>>> Case 3: Optional argument (only Option 3 is affected)
>>>> 
>>>> func nullableFoo(_: UnsafePointer<Int>?)
>>>> 
>>>> let ptrB: UnsafePointer<UInt>? = ...
>>>> 
>>>> (1) nullableFoo(UnsafePointer(cast: ptrB))
>>>> 
>>>> (2) nullableFoo(UnsafePointer(ptrB, to: A.self))
>>>> 
>>>> (3) nullableFoo(UnsafeRawPointer(ptrB).map { $0.unsafeCast(to: A.self) })
>>>> 
>>>> (4) nullableFoo(unsafeCast(rawPointer: ptrB, to: A.self))
>>> 
>>> nullableFoo(UnsafeRawPointer(ptrB)?.cast(to: UnsafePointer<A>.self))
>>> 
>>> You do the above with a failable init on UnsafeRawPointer that takes an
>>> optional UnsafePointer.
>>> 
>>>> ---
>>>> Case 4: Return values
>>>> 
>>>> func foo() -> UnsafePointer<A>
>>>> 
>>>> func caller() -> UnsafePointer<B> { ...
>>>> 
>>>> (1) return UnsafePointer(cast: foo())
>>>> 
>>>> (2) return UnsafePointer(foo(), to: B.self)
>>>> 
>>>> (3) let rawPtr = UnsafeRawPointer(foo())
>>>>   return rawPtr.unsafeCast(to: B.self)
>>>> 
>>>> (4) return unsafeCast(rawPointer: foo(), to: B.self)
>>> 
>>> return UnsafeRawPointer(foo()).cast(to: UnsafePointer<B>.self)
>>> 
>>> IMO-ly y'rs,
>>> 
>>> -- 
>>> -Dave
> 
> -- 
> Dave


More information about the swift-evolution mailing list