blob: dafe2e09baad154fc64c4fcf0952c8545258db6c [file] [log] [blame]
:orphan:
Summary
=======
Pointer arguments are a fact of life in C and Cocoa, and there's no way we're
going to realistically annotate or wrap every API safely. However, there are
plenty of well-behaved APIs that use pointer arguments in well-behaved ways
that map naturally to Swift argument conventions, and we should interact with
those APIs in a natural, Swift-ish way. To do so, I propose adding language
and library facilities that enable the following uses of pointer
arguments:
- Const pointer arguments ``const int *``, including const pointers to ObjC
classes ``NSFoo * const *``, can be used as "in" array arguments,
as ``inout`` scalar arguments, or as ``UnsafeMutablePointer`` arguments.
- Non-const pointer arguments to C types, ``int *``, can be used as ``inout``
array or scalar arguments, or as ``UnsafeMutablePointer`` arguments.
- Non-const pointer arguments to ObjC class types, ``NSFoo **``, can be used as
``inout`` scalar arguments or passed ``nil``. (They cannot be used as
array arguments or as ``UnsafeMutablePointer`` arguments.)
- ``const void *`` and ``void *`` pointers can be used in the same ways as
pointers to any C type (but not ObjC types).
This model intentionally doesn't try to provide a mapping to every possible
use case for pointers in C. It also intentionally avoids addressing special
cases we could potentially provide higher-level support for. Some particular
issues this proposal specifically does not address:
- Pointer return values
- Special handling of ``char*`` and/or ``const char*`` arguments
- Special handling of Core Foundation types
- Special handling of ``NSError**`` arguments
- Precise lifetime of values (beyond the minimal lifetime extension to the
duration of a call)
- Overriding of ObjC methods that take pointer arguments with subclass methods
that take non-pointer arguments
Design Considerations
=====================
Const Pointer Arguments
-----------------------
Arrays
~~~~~~
Const pointer arguments are frequently used in both C and Objective-C to take
an array of arguments effectively by value. To support this use case, we should
support passing a Swift ``Array`` value to a const pointer argument. An
example from Core Foundation is ``CGColorCreate``, which takes a
``CGFloat`` array of color space-specific components::
let rgb = CGColorSpaceCreateCalibratedRGB()
let cyan = CGColorCreate(rgb, [0, 1, 1])
We are willing to assume that the API is well-behaved and does not mutate the
pointed-to array, so we can safely pass an interior pointer to the array storage
without worrying about mutation. We only guarantee the lifetime of the
array for the duration of the call, so it could potentially be promoted to a
stack allocation.
"In" Arguments
~~~~~~~~~~~~~~
Const pointer arguments are also used in many cases where a value is unmodified,
but its identity is important. Somewhat more rarely, const pointer arguments
are used as pure "in" value arguments with no regard for identity; this is
particularly prevalent on platforms like Win32 where there has historically
been no standard ABI for passing structs by value, but pure "in" pointer
parameters are rare on our platforms. The potential consequences of
disregarding value identity with C APIs are too high to allow scalar arguments
to implicitly be used as pointer arguments::
// From C: void foo(const pthread_mutex_t *);
import Foo
let mutex = pthread_mutex_create()
// This would pass a different temporary buffer on each call--not what you
// want for a mutex!
foo(mutex)
foo(mutex)
foo(mutex)
Although const pointers should never be used for actual mutation, we propose
that only ``inout`` scalar arguments be accepted for const pointer parameters.
Although our semantics normally do not guarantee value identity, ``inout``
parameters that refer to stored variables or stored properties of C-derived
types are in practice never subjected to implicit writebacks except in limited
circumstances such as capture of ``inout`` references in closures that could be
diagnosed. Requiring ``inout`` also prevents the use of rvalues or ``let``
bindings that never have well-defined addresses as pointer arguments. This
more clearly communicates the intent for the callee to receive the same
variable on every call::
// From C: void foo(const pthread_mutex_t *);
import Foo
var mutex = pthread_mutex_create()
foo(&mutex)
foo(&mutex)
foo(&mutex)
If using an rvalue as a pointer argument is desired, it can easily be wrapped
in an array. This communicates that the value *is* being copied into the
temporary array, so it's more obvious that identity would not be maintained::
// an immutable scalar we might want to pass into a "const double*".
let grayLevel = 0.5
let monochrome = CGColorSpaceCreateGrayscale()
// error, can't pass Double into second argument.
let c1 = CGColorCreate(monochrome, grayval)
// error, can't take the address of a 'let' (would be ok for a 'var')
let c2 = CGColorCreate(monochrome, &grayval)
// OK, we're explicitly forming an array
let c3 = CGColorCreate(monochrome, [grayval])
Non-Const Pointer Arguments
---------------------------
C Types
~~~~~~~
Non-const arguments of C type can be used as "out" or "inout" parameters,
either of scalars or of arrays, and so should accept ``inout`` parameters of
array or scalar type. Although a C API may expect a pure "out" parameter and
not require initialization of its arguments, it is safer to assume the argument
is ``inout`` and always require initialization::
var s, c: Double
// error, 's' and 'c' aren't initialized
sincos(0.5, &s, &c)
var s1 = 0.0, c1 = 0.0
// OK
sincos(0.5, &s1, &c1)
For array parameters, the exact point of mutation inside the callee cannot be
known, so a copy-on-write array buffer must be eagerly uniqued prior to the
address of the array being taken::
func loadFloatsFromData(_ data: NSData) {
var a: [Float] = [0.0, 0.0, 0.0, 0.0]
var b = a
// Should only mutate 'b' without affecting 'a', so its backing store
// must be uniqued
data.getBytes(&b, sizeof(Float.self) * b.count)
}
ObjC Types
~~~~~~~~~~
ARC semantics treat an ``NSFoo**`` type as a pointer to an ``__autoreleasing``
``NSFoo*``. Although in theory these interfaces could receive arrays of object
pointers in Objective-C, that use case doesn't come up in Cocoa, and we can't
reliably bridge such APIs into Swift. We only need to bridge ObjC mutable pointer
types to accept a scalar ``inout`` object reference or ``nil``.
Pointer Return Values
---------------------
This proposal does not address the handling of return values, which should still
be imported into Swift as ``UnsafeMutablePointer`` values.
Library Features
================
The necessary conversions can be represented entirely in the standard library
with the help of some new language features, inout address conversion, inout
writeback conversion, and interior pointer conversion, described below. There
are three categories of argument behavior needed, and thus three new types.
These types should have no user-accessible operations of their own other than
their implicit conversions. The necessary types are as follows:
- ``CConstPointer<T>`` is the imported representation of a ``const T *``
argument. It is implicitly convertible from ``inout T`` by inout address
conversion and from ``Array<T>`` by immutable interior pointer
conversion. It is also implicitly convertible to and from ``UnsafeMutablePointer<T>``
by normal conversion.
- ``CMutablePointer<T>`` is the imported representation of a ``T *``
argument for a POD C type ``T``. It is implicitly convertible from
``inout T`` by inout address conversion and from ``inout Array<T>`` by mutating
interior pointer conversion. It is also implicitly convertible to and from
``UnsafeMutablePointer<T>`` by normal conversion.
- ``ObjCInOut<T>`` is the imported representation of a ``T **``
argument for an ObjC class type ``T``. It is implicitly convertible from
``inout T`` by inout writeback conversion and is implicitly convertible
from ``nil``. It cannot be converted from an array or to ``UnsafeMutablePointer``.
New Language Features
=====================
To support the necessary semantics for argument passing, some new conversion
forms need to be supported by the language with special-cased lifetime behavior.
Interior Pointer Conversions
----------------------------
To be able to pass a pointer to array data as an argument, we need to be able
to guarantee the lifetime of the array buffer for the duration of the call.
If mutation can potentially occur through the pointer, then copy-on-write
buffers must also be uniqued prior to taking the address. A new form of
conversion, ``@unsafe_interior_pointer_conversion``, can be applied to an
instance method of a type, to allow that type to return both a converted
pointer and an owning reference that guarantees the validity of the pointer.
Such methods can be either ``mutating`` or non-mutating; only non-mutating
conversions are considered for non- ``inout`` parameters, and only ``mutating``
conversions are considered for ``inout`` parameters::
extension Array {
@unsafe_interior_pointer_conversion
func convertToConstPointer()
-> (CConstPointer<T>, ArrayBuffer<T>) {
return (CConstPointer(self.base), self.owner)
}
@unsafe_interior_pointer_conversion
mutating func convertToMutablePointer()
-> (CMutablePointer<T>, ArrayBuffer<T>) {
// Make the backing buffer unique before handing out a mutable pointer.
self.makeUnique()
return (CMutablePointer(self.base), self.owner)
}
}
``@unsafe_interior_pointer_conversion`` conversions are only considered in
argument contexts. If such a conversion is found, the first element of the
return tuple is used as the argument, and a strong reference to the second
element is held for the duration of the callee that receives the converted
argument.
Inout Address Conversion
------------------------
To pass an ``inout`` as a pointer argument, we need to be able to lock an
address for the ``inout`` for the duration of the call, which is not normally
possible. This functionality only needs to be available to the standard library,
so can be expressed in terms of builtins. A type can conform to the
``_BuiltinInOutAddressConvertible`` protocol to be convertible from an
inout reference. The protocol is defined as follows::
protocol _BuiltinInOutAddressConvertible {
/// The type from which inout conversions are allowed to the conforming
/// type.
typealias InOutType
/// Create a value of the conforming type using the address of an inout
/// argument.
class func _convertFromInOutAddress(_ p: Builtin.RawPointer) -> Self
}
An example of a conformance for ``CMutablePointer``::
struct CMutablePointer<T>: _BuiltinInOutAddressConvertible {
let ptr: Builtin.RawPointer
typealias InOutType = T
@_transparent
static func _convertFromInOutAddress(_ p: Builtin.RawPointer)
-> CMutablePointer {
return CMutablePointer(p)
}
}
func foo(_ p: CMutablePointer<Int>) { }
var i = 0
foo(&i)
The lifetime of the variable, stored property owning object, or writeback
buffer backing the inout is guaranteed for the lifetime of the callee that
receives the converted parameter, as if the callee had received the inout
parameter directly.
Inout Writeback Conversion
--------------------------
Inout address conversion alone is not enough for ``ObjCInOut`` to work as
intended, because the change to the ``__autoreleasing`` convention for the
pointed-to object reference requires a writeback temporary. The
``_BuiltinInOutWritebackConvertible`` protocol allows for an additional
writeback to be introduced before and after the address of the ``inout`` is
taken::
protocol _BuiltinInOutWritebackConvertible {
/// The original type from which inout conversions are allowed to the
/// conforming type.
typealias InOutType
/// The type of the temporary writeback whose address is used to construct
/// the converted value.
typealias WritebackType
/// Get the initial value the writeback temporary should have on entry to
/// the call.
class func _createWriteback(inout InOutType) -> WritebackType
/// Create a value of the conforming type using the address of the writeback
/// temporary.
class func _convertFromWritebackAddress(_ p: Builtin.RawPointer) -> Self
/// Write the writeback temporary back to the original value.
class func _commitWriteback(inout InOutType, WritebackType)
}
An example of a conformance for ``ObjCInOut``::
struct ObjCInOut<T: class>: _BuiltinInOutWritebackConvertible {
let ptr: Builtin.RawPointer
typealias InOutType = T!
typealias WritebackType = Builtin.RawPointer
@_transparent
static func _createWriteback(ref: inout T!)
-> Builtin.RawPointer {
// The initial object reference is passed into the callee effectively
// __unsafe_unretained, so pass it as a RawPointer.
return unsafeBitCast(ref, Builtin.RawPointer.self)
}
@_transparent
static func _commitWriteback(ref: inout T!,
value: Builtin.RawPointer) {
// The reference is autoreleased on return from the caller, so retain it
// by loading it back as a T?.
ref = unsafeBitCast(value, T!.self)
}
@_transparent
static func _convertFromWritebackAddress(_ value: Builtin.RawPointer) {
return ObjCInOut(value)
}
}
The lifetime of the writeback is guaranteed for the lifetime of the callee that
receives the converted parameter, as if the callee had received the writeback
temporary as a mutable logical property of the original inout parameter.