Hi all,
TL;DR
I propose to replace the OpaquePointer
struct with a deprecated typealias for UnsafeRawPointer
. Then, change the import of C pointers-to-incomplete-types to produce UnsafeMutableRawPointer
or UnsafeRawPointer
, depending on whether the pointee const
.
Introduction
The standard library currently has an odd pointer type, OpaquePointer
. Better abstractions exist in the standard library for working with pointers to "raw" memory (Unsafe(Mutable)RawPointer
), so OpaquePointer
is mostly redundant and also fails to capture mutation (there is no "Mutable" variant).
I suspect that most people don't reach for OpaquePointer
. The only main source of OpaquePointer
-based APIs are imported C APIs that include pointers to incomplete types. For example, given:
void foo(struct Incomplete *arg);
void bar(const struct Incomplete *arg);
we will import both parameter types to OpaquePointer
, e.g.,
// Current
func foo(_ arg: OpaquePointer);
func bar(_ arg: OpaquePointer);
I propose to instead import using Unsafe(Mutable)RawPointer
, e.g.,
// Proposed
func foo(_ arg: UnsafeMutableRawPointer);
func bar(_ arg: UnsafeRawPointer);
This matches how we import void *
and const void*
, and allows us to simplify the standard library by removing OpaquePointer
.
Source compatibility
When we remove the OpaquePointer
struct and change the way pointers to incomplete types are imported, there are several ways in which we can break code. One obvious mitigation is to introduce a deprecated type alias for OpaquePointer
:
typealias OpaquePointer = UnsafeRawPointer
That will allow code that explicitly refers to OpaquePointer
to continue along, using UnsafeRawPointer
, because the APIs are mostly the same.
The main bit of friction comes from the fact that today one can construct an Unsafe(Mutable)RawPointer
and Unsafe(Mutable)Pointer<T>
from an OpaquePointer
(and vice versa), without regard to mutability. So, code that previously got an OpaquePointer
from a C API could then construct an UnsafeRawPointer
directly from it, e.g.,
func f() {
let p: OpaquePointer = someCAPI()
g(UnsafeMutableRawPointer(p)) // current well-formed, but would be ill-formed with this proposal
}
UnsafeMutableRawPointer
does have an initializer that allows one to create a mutable pointer from a non-mutable one, but it's called init(mutating:)
rather than init(_:)
. One can provide some measure of source compatibility by added (deprecated) initializers to UnsafeMutableRawPointer
, e.g.,
extension UnsafeMutableRawPointer {
@available(*, deprecated, renamed: "init(mutating:)")
public init(_ from : UnsafeRawPointer) {
self.init(mutating: from)
}
@available(*, deprecated, renamed: "init(mutating:)")
public init?(_ from : UnsafeRawPointer?) {
guard let from = from else { return nil }
self.init(mutating: from)
}
}
It's a little ugly, but it smoothes over most of the source compatibility concerns we've seen in practice. See the experimental implementation of this pitch for a little more commentary on the source-compatibility story. Specifically, we might also need add (deprecated) initializers to UnsafeMutablePointer.
Alternatives Considered
There are two main alternatives:
-
Leave
OpaquePointer
alone: it's ugly and crufty, but it's not worth breaking any code over such a small amount of excess API. -
Import incomplete types as distinct (but limited) types in the Swift type system. This, for example, would import the incomplete C struct "Incomplete" as a Swift struct, e.g.,
// If we imported incomplete types... @_incomplete struct Incomplete { } func foo(_ arg: UnsafeMutablePointer<Incomplete>); func bar(_ arg: UnsafePointer<Incomplete>);
This is arguably a better solution: it means that pointers to different incomplete C types (e.g.,
struct Incomplete *
andstruct OtherIncomplete *
) get imported as distinct types in Swift, which implies better type safety. However, there are two downsides:- We would need to invent a notion of incomplete types. So, while one could name
Incomplete
in a Swift program, you couldn't actually create a variable of typeIncomplete
, or instantiate a generic withIncomplete
, or any of the other things we're accustomed to doing with Swift types. Aside from the nontrivial design and implementation effort, this introduces a significant complication into the mental model for Swift. - Making imported C types more type-safe is likely to break significantly more existing source code.
Given those two downsides, I feel that it's better to make a limited change here (eliminating
OpaquePointer
) and move on---we don't need to expend significant effort to improve C interoperability further, and it's certainly not worth breaking much code over. - We would need to invent a notion of incomplete types. So, while one could name
So, what do we think? Is it worth trying to get rid of OpaquePointer
?
Doug