[swift-evolution] [Draft] UnsafeRawPointer API

Dave Abrahams dabrahams at apple.com
Mon Jun 27 01:39:37 CDT 2016


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

    let p = UnsafePointer(r, pointee: Int.self)

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.  Also, it reads like we're casting the raw pointer to an
Int, rather than to an UnsafePointer<Int>.  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


More information about the swift-evolution mailing list