mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
1741 lines
74 KiB
ReStructuredText
1741 lines
74 KiB
ReStructuredText
:orphan:
|
|
|
|
.. default-role:: term
|
|
.. title:: Library Evolution Support in Swift ("Resilience")
|
|
|
|
:Author: Jordan Rose
|
|
:Author: John McCall
|
|
|
|
.. note::
|
|
|
|
This document uses some Sphinx-specific features which are not available on
|
|
GitHub. For proper rendering, download and build the docs yourself. Jordan
|
|
Rose also posts occasional snapshots at
|
|
https://jrose-apple.github.io/swift-library-evolution/.
|
|
|
|
One of Swift's primary design goals is to allow efficient execution of code
|
|
without sacrificing load-time abstraction of implementation.
|
|
|
|
Abstraction of implementation means that code correctly written against a
|
|
published interface will correctly function when the underlying implementation
|
|
changes to anything which still satisfies the original interface. There are
|
|
many potential reasons to provide this sort of abstraction. Apple's primary
|
|
interest is in making it easy and painless for our internal and external
|
|
developers to improve the ecosystem of Apple products by creating good and
|
|
secure programs and libraries; subtle deployment problems and/or unnecessary
|
|
dependencies on the behavior of our implementations would work against these
|
|
goals.
|
|
|
|
Our current design in Swift is to provide opt-out load-time abstraction of
|
|
implementation for all language features. Alone, this would either incur
|
|
unacceptable cost or force widespread opting-out of abstraction. We intend to
|
|
mitigate this primarily by designing the language and its implementation to
|
|
minimize unnecessary and unintended abstraction:
|
|
|
|
* Avoiding unnecessary language guarantees and taking advantage of that
|
|
flexibility to limit load-time costs.
|
|
|
|
* Within the domain that defines an entity, all the details of its
|
|
implementation are available.
|
|
|
|
* When entities are not exposed outside their defining module, their
|
|
implementation is not constrained.
|
|
|
|
* By default, entities are not exposed outside their defining modules. This is
|
|
independently desirable to reduce accidental API surface area, but happens to
|
|
also interact well with the performance design.
|
|
|
|
This last point is a specific case of a general tenet of Swift: **the default
|
|
behavior is safe**. Where possible, choices made when an entity is first
|
|
published should not limit its evolution in the future.
|
|
|
|
.. contents:: :local:
|
|
|
|
|
|
Introduction
|
|
============
|
|
|
|
This model is intended to serve library designers whose libraries will evolve
|
|
over time. Such libraries must be both `backwards-compatible`, meaning that
|
|
existing clients should continue to work even when the library is updated, and
|
|
`forwards-compatible`, meaning that future clients will be able run using the
|
|
current version of the library. In simple terms:
|
|
|
|
- Last year's apps should work with this year's library.
|
|
- Next year's apps should work with this year's library.
|
|
|
|
This document will frequently refer to a *library* which vends public APIs, and
|
|
a single *client* that uses them. The same principles apply even when multiple
|
|
libraries and multiple clients are involved.
|
|
|
|
This document is primarily concerned with `binary compatibility`, i.e. what
|
|
changes can safely be made to a library between releases that will not break
|
|
memory-safety or type-safety, or cause clients to fail to run at all. A
|
|
secondary concern is identifying `binary-compatible source-breaking changes
|
|
<binary-compatible source-breaking change>`, where clients compiled against the
|
|
previous version of a library are likely to behave differently than clients
|
|
compiled against the new version of the library.
|
|
|
|
.. note::
|
|
|
|
These rules do not (and cannot) guarantee that a change is *semantically*
|
|
backwards-compatible or forwards-compatible. *Any* change to a library's
|
|
existing API that affects its observable behavior may affect clients. It is
|
|
the responsibility of a library author to be sure that the changes they are
|
|
making are *semantically* correct, preserving the preconditions,
|
|
postconditions, and invariants of previously-published APIs.
|
|
|
|
This model is largely not of interest to libraries that are bundled with their
|
|
clients (distribution via source, static library, or embedded/sandboxed dynamic
|
|
library, as used by the `Swift Package Manager`_). Because a client always uses
|
|
a particular version of such a library, there is no need to worry about
|
|
backwards- or forwards-compatibility. Just as developers with a single app
|
|
target are not forced to think about access control, anyone writing a bundled
|
|
library should not be required to use any of the annotations described below in
|
|
order to achieve full performance.
|
|
|
|
.. _Swift Package Manager: https://swift.org/package-manager/
|
|
|
|
.. note::
|
|
|
|
This model may, however, be useful for library authors that want to
|
|
preserve *source* compatibility, and it is hoped that the tool for
|
|
`Checking Binary Compatibility`_ described below will also be useful for
|
|
this purpose. Additionally, we may decide to use some of these annotations
|
|
as performance hints for *non-*\ optimized builds.
|
|
|
|
The term "resilience" comes from the occasional use of "fragile" to describe
|
|
certain constructs that have very strict binary compatibility rules. For
|
|
example, a client's use of a C struct is "fragile" in that if the library
|
|
changes the fields in the struct, the client's use will "break". In Swift,
|
|
changing the fields in a struct will not automatically cause problems for
|
|
existing clients, so we say the struct is "resilient".
|
|
|
|
|
|
Using Versioned API
|
|
===================
|
|
|
|
References to a versioned API must always be guarded with the appropriate
|
|
availability checks. This means that any client entities that rely on certain
|
|
APIs from a library must themselves be restricted to contexts in which those
|
|
APIs are available. This is accomplished using the ``@available`` attribute, by
|
|
specifying the name of the client library along with the required version::
|
|
|
|
// Client code
|
|
@available(Magician 1.5)
|
|
class CrystalBallView : MagicView { /*...*/ }
|
|
|
|
Library versions can also be checked dynamically using ``#available``, allowing
|
|
for fallback behavior when the requested library version is not present::
|
|
|
|
func scareMySiblings() {
|
|
if #available(Magician 1.2) {
|
|
summonDemons()
|
|
} else {
|
|
print("BOO!!")
|
|
}
|
|
}
|
|
|
|
.. note::
|
|
|
|
Possible implementations include generating a hidden symbol into a library,
|
|
or putting the version number in some kind of metadata, like the Info.plist
|
|
in a framework bundle on Darwin platforms.
|
|
|
|
This is essentially the same model as the availability checking released in
|
|
Swift 2.0, but generalized for checking library versions instead of just OS
|
|
versions.
|
|
|
|
|
|
Declaring Library Version Dependencies
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Swift's current availability model includes the notion of a *minimum deployment
|
|
target,* the version of an OS that must be present for the program being
|
|
compiled to run at all. For example, a program compiled with a minimum
|
|
deployment target of iOS 9.2 will not launch on iOS 9.0.
|
|
|
|
The generalized model above suggests being able to make similar guarantees for
|
|
individual libraries. For example, a client program may depend on version 1.1
|
|
of the "Magician" library; trying to run using version 1.0 will result in
|
|
errors. By declaring this at compile-time, the client code can omit
|
|
``@available`` and ``#available`` checks that are satisfied by the minimum
|
|
library version.
|
|
|
|
Both the syntax and enforcement of this feature are not covered by this
|
|
document.
|
|
|
|
|
|
Publishing Versioned API
|
|
========================
|
|
|
|
A library's API is already marked with the ``public`` modifier, but if a
|
|
client wants to work with multiple releases of the library, the API needs
|
|
versioning information as well. A *versioned entity* represents anything with a
|
|
runtime presence that a client may rely on; its version records when the entity
|
|
was first exposed publicly in its library. Put another way, it is the oldest
|
|
version of the library where the entity may be used.
|
|
|
|
- Classes, structs, enums, and protocols may all be versioned entities.
|
|
- Methods, properties, subscripts, and initializers may be versioned entities.
|
|
- Top-level functions, variables, and constants may be versioned entities.
|
|
- Protocol conformances may be versioned entities, despite not explicitly having
|
|
a declaration in Swift, because a client may depend on them.
|
|
See `New Conformances`_, below.
|
|
|
|
In a versioned library, any top-level public entity from the list above may not
|
|
be made ``public`` (or ``open``) without an appropriate version. A public
|
|
entity declared within a versioned type (or an extension of a versioned type)
|
|
will default to having the same version as the type.
|
|
|
|
In this document, the term "public" includes classes and members marked
|
|
``open``.
|
|
|
|
Code within a library may generally use all other entities declared within the
|
|
library (barring their own availability checks), since the entire library is
|
|
shipped as a unit. That is, even if a particular API was introduced in v1.0,
|
|
its (non-public) implementation may refer to APIs introduced in later versions.
|
|
|
|
Certain uses of ``internal`` entities require them to be part of a library's
|
|
binary interface, which means they need to be versioned as well. See
|
|
`Versioning Internal Declarations`_ below.
|
|
|
|
In addition to versioned entities, there are also attributes that are safe to
|
|
add to declarations when releasing a new version of a library. In most cases,
|
|
clients can only take advantage of the attributes when using the new release of
|
|
the library, and therefore the attributes also need to record the version in
|
|
which they were introduced; these are called *versioned attributes.* If the
|
|
version is omitted, it is assumed to be the version of the declaration to which
|
|
the attribute is attached.
|
|
|
|
The syntax for marking an entity as versioned has not yet been decided, but the
|
|
rest of this document will use syntax #1 described below.
|
|
|
|
|
|
Syntax #1: Attributes
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
::
|
|
|
|
@available(1.2)
|
|
public func summonDemons()
|
|
|
|
@available(1.0) @inlineable(1.2)
|
|
public func summonElves()
|
|
|
|
Using the same attribute for both publishing and using versioned APIs helps tie
|
|
the feature together and enforces a consistent set of rules. However, there are
|
|
several other annotations described later in this document that also need
|
|
versioning information, and it may not be obvious what the version number means
|
|
outside the context of ``available``.
|
|
|
|
|
|
Syntax #2: Version Blocks
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
::
|
|
|
|
#version(1.2)
|
|
public func summonDemons()
|
|
|
|
#version(1.0) {}
|
|
#version(1.2) { @inlineable }
|
|
public func summonElves()
|
|
|
|
Since there are potentially many annotations on a declaration that need
|
|
versioning information, it may make sense to group them together in some way.
|
|
Only certain annotations would support being versioned in this way.
|
|
|
|
|
|
Syntax #3: The ``public`` modifier
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
::
|
|
|
|
public(1.2) func summonDemons()
|
|
|
|
/* @inlineable ?? */
|
|
public(1.0) func summonElves()
|
|
|
|
Putting the version on the public modifier is the most concise option. However,
|
|
there's no obvious syntax here for adding versions to other annotations that
|
|
may apply to a declaration.
|
|
|
|
(Also, at one point there was a proposal to tag API only intended for certain
|
|
clients using a similar syntax: ``public("Foundation")``, for example, for APIs
|
|
only meant to be used by Foundation. These could then be stripped out of the
|
|
public interface for a framework before being widely distributed. But that
|
|
could easily use an alternate syntax.)
|
|
|
|
|
|
Supported Evolution
|
|
===================
|
|
|
|
This section describes the various changes that are safe to make when releasing
|
|
a new version of a library, i.e. changes that will not break binary
|
|
compatibility. They are organized by declaration type.
|
|
|
|
Anything *not* listed in this document should be assumed unsafe.
|
|
|
|
|
|
Top-Level Functions
|
|
~~~~~~~~~~~~~~~~~~~
|
|
|
|
A versioned top-level function is fairly restricted in how it can be changed.
|
|
The following changes are permitted:
|
|
|
|
- Changing the body of the function.
|
|
- Changing *internal* parameter names (i.e. the names used within the function
|
|
body, not the labels that are part of the function's full name).
|
|
- Reordering generic requirements (but not the generic parameters themselves).
|
|
- Adding a default argument expression to a parameter.
|
|
- Changing or removing a default argument is a `binary-compatible
|
|
source-breaking change`.
|
|
- The ``@discardableResult`` and ``@warn_unqualified_access`` attributes may
|
|
be added to a function without any additional versioning information.
|
|
|
|
No other changes are permitted; the following are particularly of note:
|
|
|
|
- A versioned function may not change its parameters or return type.
|
|
- A versioned function may not change its generic requirements.
|
|
- A versioned function may not change its external parameter names (labels).
|
|
- A versioned function may not add, remove, or reorder parameters, whether or
|
|
not they have default arguments.
|
|
- A versioned function that throws may not become non-throwing or vice versa.
|
|
- The ``@escaping`` attribute may not be added to or removed from a parameter.
|
|
It is not a `versioned attribute` and so there is no way to guarantee that it
|
|
is safe when a client deploys against older versions of the library.
|
|
|
|
|
|
Inlineable Functions
|
|
--------------------
|
|
|
|
Functions are a very common example of resilience: the function's declaration
|
|
is published as API, but its body may change between library versions as long
|
|
as it upholds the same semantic contracts. This applies to other function-like
|
|
constructs as well: initializers, accessors, and deinitializers.
|
|
|
|
However, sometimes it is useful to provide the body to clients as well. There
|
|
are a few common reasons for this:
|
|
|
|
- The function only performs simple operations, and so inlining it will both
|
|
save the overhead of a cross-library function call and allow further
|
|
optimization of callers.
|
|
|
|
- The function accesses a fixed-contents struct with non-public members; this
|
|
allows the library author to preserve invariants while still allowing
|
|
efficient access to the struct.
|
|
|
|
- The function is used to determine which version of the library a client was
|
|
compiled against.
|
|
|
|
- The library author does not want to make the function part of their binary
|
|
interface, allowing for source-compatible changes
|
|
|
|
A versioned function marked with the ``@inlineable`` attribute makes its body
|
|
available to clients as part of the module's public interface. ``@inlineable``
|
|
is a `versioned attribute`; clients may not assume that the body of the
|
|
function is suitable when deploying against older versions of the library. If a
|
|
function has been inlineable since it was introduced, it is not considered part
|
|
of the library's binary interface.
|
|
|
|
Clients are not required to inline a function marked ``@inlineable``. However,
|
|
if a function has been inlineable since the minimum required version of the
|
|
library, any use of that function must copy its implementation into the client
|
|
module.
|
|
|
|
.. note::
|
|
|
|
It is legal to change the implementation of an inlineable function in the
|
|
next release of the library. However, any such change must be made with the
|
|
understanding that it will not affect existing clients. This is the
|
|
standard example of a `binary-compatible source-breaking change`.
|
|
|
|
Any local functions or closures within an inlineable function are themselves
|
|
treated as ``@inlineable``. This is important in case it is necessary to change
|
|
the inlineable function later; existing clients should not be depending on
|
|
internal details of the previous implementation.
|
|
|
|
It is a `binary-compatible source-breaking change` to completely remove a
|
|
public entity marked ``@inlineable`` from a library if and only if it has been
|
|
inlineable since it was introduced. If not, it is considered a part of the
|
|
library's binary interface and may not be removed.
|
|
|
|
(Non-public, non-versioned entities may always be removed from a library; they
|
|
are not part of its API or ABI.)
|
|
|
|
.. note::
|
|
|
|
Removing ``@inlineable`` from a public entity without updating its
|
|
availability information is not permitted. Instead, add a second,
|
|
non-inlineable function that provides the implementation of the
|
|
``@inlineable`` entity, and call through to that function when a new enough
|
|
version of the library is available.
|
|
|
|
Although they are not a supported feature for arbitrary libraries at this time,
|
|
`transparent`_ functions are implicitly marked ``@inlineable``.
|
|
|
|
.. _transparent: https://github.com/apple/swift/blob/master/docs/TransparentAttr.rst
|
|
|
|
|
|
Restrictions on Inlineable Functions
|
|
------------------------------------
|
|
|
|
Because the body of an inlineable function (or method, accessor, initializer,
|
|
or deinitializer) will be inlined into another module, it must not make any
|
|
assumptions that rely on knowledge of the current module. Here is a trivial
|
|
example using methods::
|
|
|
|
public struct Point2D {
|
|
var x, y: Double
|
|
public init(x: Double, y: Double) { /*...*/ }
|
|
}
|
|
|
|
extension Point2D {
|
|
@inlineable public func distance(to other: Point2D) -> Double {
|
|
let deltaX = self.x - other.x
|
|
let deltaY = self.y - other.y
|
|
return sqrt(deltaX*deltaX + deltaY*deltaY)
|
|
}
|
|
}
|
|
|
|
As written, this ``distance`` method is not safe to inline. The next release
|
|
of the library could very well replace the implementation of ``Point2D`` with a
|
|
polar representation::
|
|
|
|
public struct Point2D {
|
|
var r, theta: Double
|
|
public init(x: Double, y: Double) { /*...*/ }
|
|
}
|
|
|
|
and the ``x`` and ``y`` properties have now disappeared. To avoid this, the
|
|
bodies of inlineable functions have the following restrictions:
|
|
|
|
- They may not define any local types (other than typealiases).
|
|
|
|
- They must not reference any ``private`` or ``fileprivate`` entities, except
|
|
for other declarations marked ``@inlineable``.
|
|
|
|
- They must not reference any ``internal`` entities except for those that have
|
|
been `versioned`_ and those declared ``@inlineable``. See below for a
|
|
discussion of versioning internal API.
|
|
|
|
- In addition, no non-public, non-versioned stored constants or variables may
|
|
be referenced even if they are declared ``@inlineable``. (This is because
|
|
their storage is still considered part of the defining library.)
|
|
|
|
- They must not reference any entities from the current module introduced
|
|
after the function was made inlineable, except under appropriate availability
|
|
guards.
|
|
|
|
.. _versioned: #versioning-internal-api
|
|
|
|
|
|
Default Argument Expressions
|
|
----------------------------
|
|
|
|
Default argument expressions are implemented as ``@inlineable`` functions and
|
|
thus are subject to the same restrictions as inlineable functions. A default
|
|
argument implicitly has the same availability as the function it is attached to.
|
|
|
|
.. note::
|
|
|
|
Swift 4.0's implementation of default arguments puts the evaluation of the
|
|
default argument expression in the library, rather than in the client like
|
|
C++ or C#. We plan to change this.
|
|
|
|
|
|
Top-Level Variables and Constants
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Given a versioned module-scope variable declared with ``var``, the following
|
|
changes are permitted:
|
|
|
|
- Adding (but not removing) a public setter to a computed variable.
|
|
- Adding or removing a non-public, non-versioned setter.
|
|
- Changing from a stored variable to a computed variable, or vice versa, as
|
|
long as a previously versioned setter is not removed.
|
|
- Changing the body of an accessor.
|
|
- Adding or removing an observing accessor (``willSet`` or ``didSet``) to/from
|
|
an existing variable. This is effectively the same as modifying the body of a
|
|
setter.
|
|
- Changing the initial value of a stored variable.
|
|
- Adding or removing ``weak`` from a variable with ``Optional`` type.
|
|
- Adding or removing ``unowned`` from a variable.
|
|
- Adding or removing ``@NSCopying`` to/from a variable.
|
|
|
|
If a public setter is added after the property is first exposed (whether the
|
|
property is stored or computed), it must be versioned independently of the
|
|
property itself.
|
|
|
|
.. admonition:: TODO
|
|
|
|
This needs syntax.
|
|
|
|
Additionally, for a module-scope constant declared with ``let``, the following
|
|
changes are permitted:
|
|
|
|
- Changing the value of the constant.
|
|
|
|
It is *not* safe to change a ``let`` constant into a variable or vice versa.
|
|
Top-level constants are assumed not to change for the entire lifetime of the
|
|
program once they have been initialized.
|
|
|
|
.. admonition:: TODO
|
|
|
|
We could make it safe to turn a read-only ``var`` into a ``let``, but do we
|
|
want to? We would have to come up with syntax for declaring when it
|
|
changed, at least.
|
|
|
|
|
|
Giving Up Flexibility
|
|
---------------------
|
|
|
|
Both top-level constants and variables can be marked ``@inlineable`` to allow
|
|
clients to access them more efficiently. This restricts changes a fair amount:
|
|
|
|
- Adding a versioned setter to a computed variable is still permitted.
|
|
- Adding or removing a non-public, non-versioned setter is still permitted.
|
|
- Changing from stored to computed or vice versa is forbidden, because it would
|
|
break existing clients.
|
|
- Changing the body of an accessor is a `binary-compatible source-breaking
|
|
change`.
|
|
- Adding/removing observing accessors is likewise a `binary-compatible
|
|
source-breaking change`.
|
|
- Changing the initial value of a stored variable is still permitted.
|
|
- Changing the value of a constant is a `binary-compatible source-breaking
|
|
change`.
|
|
- Adding or removing ``weak`` is forbidden.
|
|
- Adding or removing ``unowned`` is forbidden.
|
|
- Adding or removing ``@NSCopying`` to/from a variable is `binary-compatible
|
|
source-breaking change`.
|
|
|
|
.. admonition:: TODO
|
|
|
|
It Would Be Nice(tm) to allow marking the *getter* of a top-level variable
|
|
inlineable while still allowing the setter to change. This would need
|
|
syntax, though.
|
|
|
|
Any inlineable accessors must follow the rules for `inlineable functions`_, as
|
|
described above.
|
|
|
|
Unlike functions, inlineable stored constants and variables (including those
|
|
with accessors) may not be removed even if they have been inlineable since they
|
|
were first introduced, because the storage for these bindings is still part of
|
|
the library in which they were defined. Removing computed variables that have
|
|
been inlineable since their introduction is a `binary-compatible
|
|
source-breaking change`.
|
|
|
|
Note that if a constant's initial value expression has any observable side
|
|
effects, including the allocation of class instances, it must not be treated
|
|
as inlineable. A constant must always behave as if it is initialized exactly
|
|
once.
|
|
|
|
.. admonition:: TODO
|
|
|
|
Is this a condition we can detect at compile-time? Do we have to be
|
|
restricted to things that can be lowered to compile-time constants?
|
|
|
|
|
|
Structs
|
|
~~~~~~~
|
|
|
|
Swift structs are a little more flexible than their C counterparts. By default,
|
|
the following changes are permitted:
|
|
|
|
- Reordering any existing members, including stored properties.
|
|
- Adding any new members, including stored properties.
|
|
- Changing existing properties from stored to computed or vice versa.
|
|
- Changing the body of any methods, initializers, or accessors.
|
|
- Adding or removing an observing accessor (``willSet`` or ``didSet``) to/from
|
|
an existing property. This is effectively the same as modifying the body of a
|
|
setter.
|
|
- Removing any non-public, non-versioned members, including stored properties.
|
|
- Adding a new protocol conformance (with proper availability annotations).
|
|
- Removing conformances to non-public protocols.
|
|
|
|
The important most aspect of a Swift struct is its value semantics, not its
|
|
layout.
|
|
|
|
It is not safe to add or remove ``mutating`` or ``nonmutating`` from a member
|
|
or accessor within a struct. These modifiers are not `versioned attributes
|
|
<versioned attribute>` and as such there is no safety guarantee for a client
|
|
deploying against an earlier version of the library.
|
|
|
|
|
|
Methods and Initializers
|
|
------------------------
|
|
|
|
For the most part struct methods and initializers are treated exactly like
|
|
top-level functions. They permit all of the same modifications and can also be
|
|
marked ``@inlineable``, with the same restrictions. Inlineable initializers
|
|
must always delegate to another initializer, since new properties may be added
|
|
between new releases. For the same reason, initializers declared outside of the
|
|
struct's module must always delegate to another initializer.
|
|
|
|
|
|
Properties
|
|
----------
|
|
|
|
Struct properties behave largely the same as top-level bindings. They permit
|
|
all of the same modifications, and also allow adding or removing an initial
|
|
value entirely.
|
|
|
|
Struct properties can also be marked ``@inlineable``, with the same
|
|
restrictions as for top-level bindings. An inlineable stored property may not
|
|
become computed, but the offset of its storage within the struct is not
|
|
necessarily fixed.
|
|
|
|
Like top-level constants, it is *not* safe to change a ``let`` property into a
|
|
variable or vice versa. Properties declared with ``let`` are assumed not to
|
|
change for the entire lifetime of the program once they have been initialized.
|
|
|
|
|
|
Subscripts
|
|
----------
|
|
|
|
Subscripts behave largely the same as properties, except that there are no
|
|
stored subscripts. This means that the following changes are permitted:
|
|
|
|
- Adding (but not removing) a public setter.
|
|
- Adding or removing a non-public, non-versioned setter.
|
|
- Changing the body of an accessor.
|
|
- Changing index parameter internal names (i.e. the names used within the
|
|
accessor bodies, not the labels that are part of the subscript's full name).
|
|
- Reordering generic requirements (but not the generic parameters themselves).
|
|
- Adding a default argument expression to an index parameter.
|
|
- Changing or removing a default argument is a `binary-compatible
|
|
source-breaking change`.
|
|
|
|
Like properties, subscripts can be marked ``@inlineable``, which makes changing
|
|
the body of an accessor a `binary-compatible source-breaking change`. Any
|
|
inlineable accessors must follow the rules for `inlineable functions`_, as
|
|
described above.
|
|
|
|
|
|
New Conformances
|
|
----------------
|
|
|
|
If a conformance is added to a type in version 1.1 of a library, it's important
|
|
that it isn't accessed in version 1.0. This is implied if the protocol itself
|
|
was introduced in version 1.1, but needs special handling if both the protocol
|
|
and the type were available earlier. In this case, the conformance *itself*
|
|
needs to be labeled as being introduced in version 1.1, so that the compiler
|
|
can enforce its safe use.
|
|
|
|
.. note::
|
|
|
|
This may feel like a regression from Objective-C, where `duck typing` would
|
|
allow a ``Wand`` to be passed as an ``id <MagicType>`` without ill effects.
|
|
However, ``Wand`` would still fail a ``-conformsToProtocol:`` check in
|
|
version 1.0 of the library, and so whether or not the client code will work
|
|
is dependent on what should be implementation details of the library.
|
|
|
|
We've considered two possible syntaxes for this::
|
|
|
|
@available(1.1)
|
|
extension Wand : MagicType {/*...*/}
|
|
|
|
and
|
|
|
|
::
|
|
|
|
extension Wand : @available(1.1) MagicType {/*...*/}
|
|
|
|
The former requires fewer changes to the language grammar, but the latter could
|
|
also be used on the declaration of the type itself (i.e. the ``struct``
|
|
declaration).
|
|
|
|
If we went with the former syntax, applying ``@available`` to an extension
|
|
would override the default availability of entities declared within the
|
|
extension; unlike access control, entities within the extension may freely
|
|
declare themselves to be either more or less available than what the extension
|
|
provides.
|
|
|
|
|
|
Fixed-Contents Structs
|
|
----------------------
|
|
|
|
To opt out of this flexibility, a struct may be marked ``@fixedContents``.
|
|
This promises that no stored properties will be added to or removed from the
|
|
struct, even non-public ones. Additionally, all versioned instance stored
|
|
properties in a ``@fixedContents`` struct are implicitly declared
|
|
``@inlineable`` (as described above for top-level variables). In effect:
|
|
|
|
- Reordering stored instance properties (public or non-public) is not permitted.
|
|
Reordering all other members is still permitted.
|
|
- Adding new stored instance properties (public or non-public) is not permitted.
|
|
Adding any other new members is still permitted.
|
|
- Existing instance properties may not be changed from stored to computed or
|
|
vice versa.
|
|
- Changing the body of any *existing* methods, initializers, computed property
|
|
accessors, or non-instance stored property accessors is permitted. Changing
|
|
the body of a stored instance property observing accessor is permitted if the
|
|
property is not `versioned <versioned entity>`, and considered a
|
|
`binary-compatible source-breaking change` if it is.
|
|
- Adding or removing observing accessors from any
|
|
`versioned <versioned entity>` stored instance properties (public or
|
|
non-public) is not permitted.
|
|
- Removing stored instance properties is not permitted. Removing any other
|
|
non-public, non-versioned members is still permitted.
|
|
- Adding a new protocol conformance is still permitted.
|
|
- Removing conformances to non-public protocols is still permitted.
|
|
|
|
Additionally, if the type of any stored instance property includes a struct or
|
|
enum, that struct or enum must be `versioned <versioned entity>`. This includes
|
|
generic parameters and members of tuples.
|
|
|
|
.. note::
|
|
|
|
The name ``@fixedContents`` is intentionally awful to encourage us to come
|
|
up with a better one.
|
|
|
|
While adding or removing stored properties is forbidden, existing properties may
|
|
still be modified in limited ways:
|
|
|
|
- An existing non-public, non-versioned property may change its access level to
|
|
any other non-public access level.
|
|
- A non-versioned ``internal`` property may be versioned (see `Versioning
|
|
Internal Declarations`_).
|
|
- A versioned ``internal`` property may be made ``public`` (without changing
|
|
its version).
|
|
|
|
An initializer of a fixed-contents struct may be declared ``@inlineable`` even
|
|
if it does not delegate to another initializer, as long as the ``@inlineable``
|
|
attribute, or the initializer itself, is not introduced earlier than the
|
|
``@fixedContents`` attribute and the struct has no non-versioned stored
|
|
properties.
|
|
|
|
A ``@fixedContents`` struct is *not* guaranteed to use the same layout as a C
|
|
struct with a similar "shape". If such a struct is necessary, it should be
|
|
defined in a C header and imported into Swift.
|
|
|
|
.. note::
|
|
|
|
We can add a *different* feature to control layout some day, or something
|
|
equivalent, but this feature should not restrict Swift from doing useful
|
|
things like minimizing member padding.
|
|
|
|
.. note::
|
|
|
|
Hypothetically, we could use a different model where a ``@fixedContents``
|
|
struct only guarantees the "shape" of the struct, so to speak, while
|
|
leaving all property accesses to go through function calls. This would
|
|
allow stored properties to change their accessors, or (with the Behaviors
|
|
proposal) to change a behavior's implementation, or change from one
|
|
behavior to another. However, the *most common case* here is probably just
|
|
a simple C-like struct that groups together simple values, with only public
|
|
stored properties and no observing accessors, and having to opt into direct
|
|
access to those properties seems unnecessarily burdensome. The struct is
|
|
being declared ``@fixedContents`` for a reason, after all: it's been
|
|
discovered that its use is causing performance issues.
|
|
|
|
Consequently, as a first pass we may just require all stored properties in
|
|
a ``@fixedContents`` struct, public or non-public, to have trivial
|
|
accessors, i.e. no observing accessors and no behaviors.
|
|
|
|
``@fixedContents`` is a `versioned attribute`. This is so that clients can
|
|
deploy against older versions of the library, which may have a different layout
|
|
for the struct. (In this case the client must manipulate the struct as if the
|
|
``@fixedContents`` attribute were absent.)
|
|
|
|
|
|
Enums
|
|
~~~~~
|
|
|
|
By default, a library owner may add new cases to a public enum between releases
|
|
without breaking binary compatibility. As with structs, this results in a fair
|
|
amount of indirection when dealing with enum values, in order to potentially
|
|
accommodate new values. More specifically, the following changes are permitted:
|
|
|
|
- Adding a new case.
|
|
- Reordering existing cases is a `binary-compatible source-breaking change`. In
|
|
particular, if an enum is RawRepresentable, changing the raw representations
|
|
of cases may break existing clients who use them for serialization.
|
|
- Adding a raw type to an enum that does not have one.
|
|
- Removing a non-public, non-versioned case.
|
|
- Adding any other members.
|
|
- Removing any non-public, non-versioned members.
|
|
- Adding a new protocol conformance (with proper availability annotations).
|
|
- Removing conformances to non-public protocols.
|
|
|
|
.. note::
|
|
|
|
If an enum value has a known case, or can be proven to belong to a set of
|
|
known cases, the compiler is of course free to use a more efficient
|
|
representation for the value, just as it may discard fields of structs that
|
|
are provably never accessed.
|
|
|
|
.. note::
|
|
|
|
Non-public cases in public enums don't exist at the moment, but they *can*
|
|
be useful, and they require essentially the same implementation work as
|
|
cases added in future versions of a library.
|
|
|
|
Adding or removing the ``@objc`` attribute from an enum is not permitted; this
|
|
affects the enum's memory representation and is not backwards-compatible.
|
|
|
|
|
|
Initializers
|
|
------------
|
|
|
|
For the most part enum initializers are treated exactly like top-level
|
|
functions. They permit all of the same modifications and can also be marked
|
|
``@inlineable``, with the same restrictions. Unlike struct initializers, enum
|
|
initializers do not always need to delegate to another initializer, even if
|
|
they are inlineable or declared in a separate module.
|
|
|
|
|
|
Methods and Subscripts
|
|
----------------------
|
|
|
|
The rules for enum methods and subscripts are identical to those for struct
|
|
members.
|
|
|
|
|
|
Closed Enums
|
|
------------
|
|
|
|
A library owner may opt out of this flexibility by marking a versioned enum as
|
|
``@closed``. A "closed" enum may not have any cases with less access than the
|
|
enum itself, and may not add new cases in the future. This guarantees to
|
|
clients that the enum cases are exhaustive. In particular:
|
|
|
|
- Adding new cases is not permitted.
|
|
- Reordering existing cases is not permitted.
|
|
- Adding a raw type to an enum that does not have one is not permitted -- it's
|
|
used for optimization.
|
|
- Removing a non-public case is not applicable.
|
|
- Adding any other members is still permitted.
|
|
- Removing any non-public, non-versioned members is still permitted.
|
|
- Adding a new protocol conformance is still permitted.
|
|
- Removing conformances to non-public protocols is still permitted.
|
|
|
|
.. note::
|
|
|
|
Were a public "closed" enum allowed to have non-public cases, clients of
|
|
the library would still have to treat the enum as opaque and would still
|
|
have to be able to handle unknown cases in their ``switch`` statements.
|
|
|
|
``@closed`` is a `versioned attribute`. This is so that clients can deploy
|
|
against older versions of the library, which may have non-public cases in the
|
|
enum. (In this case the client must manipulate the enum as if the ``@closed``
|
|
attribute were absent.) All cases that are not versioned become implicitly
|
|
versioned with this number.
|
|
|
|
Even for default "open" enums, adding new cases should not be done lightly. Any
|
|
clients attempting to do an exhaustive switch over all enum cases will likely
|
|
not handle new cases well.
|
|
|
|
.. note::
|
|
|
|
One possibility would be a way to map new cases to older ones on older
|
|
clients. This would only be useful for certain kinds of enums, though, and
|
|
adds a lot of additional complexity, all of which would be tied up in
|
|
versions. Our generalized switch patterns probably make it hard to nail
|
|
down the behavior here.
|
|
|
|
|
|
Protocols
|
|
~~~~~~~~~
|
|
|
|
There are very few safe changes to make to protocols and their members:
|
|
|
|
- A new non-type requirement may be added to a protocol, as long as it has an
|
|
unconstrained default implementation.
|
|
- A new associated type may be added to a protocol, as long as it has a default.
|
|
As with any other declarations, newly-added associated types must be marked
|
|
with ``@available`` specifying when they were introduced.
|
|
- A default may be added to an associated type.
|
|
- A new optional requirement may be added to an ``@objc`` protocol.
|
|
- All members may be reordered, including associated types.
|
|
- Changing *internal* parameter names of function and subscript requirements
|
|
is permitted.
|
|
- Reordering generic requirements is permitted (but not the generic parameters
|
|
themselves).
|
|
- The ``@discardableResult`` and ``@warn_unqualified_access`` attributes may
|
|
be added to a function requirement without any additional versioning
|
|
information.
|
|
|
|
All other changes to the protocol itself are forbidden, including:
|
|
|
|
- Making an existing requirement optional.
|
|
- Making a non-``@objc`` protocol ``@objc`` or vice versa.
|
|
- Adding or removing constraints from an associated type.
|
|
- Removing a default from an associated type.
|
|
|
|
Protocol extensions may be more freely modified; `see below`__.
|
|
|
|
__ #protocol-extensions
|
|
|
|
.. note::
|
|
|
|
Allowing the addition of associated types means implementing some form of
|
|
"generalized existentials", so that existing existential values (values
|
|
with protocol type) continue to work even if a protocol gets its first
|
|
associated type. Until we have that feature implemented, it would only be
|
|
safe to add an associated type to a protocol that already has associated
|
|
types, or uses ``Self`` in a non-return position (i.e. one that currently
|
|
cannot be used as the type of a value).
|
|
|
|
As a first pass, it is likely that we will not allow adding new associated
|
|
types to existing protocols at all.
|
|
|
|
|
|
Classes
|
|
~~~~~~~
|
|
|
|
Because class instances are always accessed through references, they are very
|
|
flexible and can change in many ways between releases. Like structs, classes
|
|
support all of the following changes:
|
|
|
|
- Reordering any existing members, including stored properties.
|
|
- Changing existing properties from stored to computed or vice versa.
|
|
- Changing the body of any methods, initializers, or accessors.
|
|
- Adding or removing an observing accessor (``willSet`` or ``didSet``) to/from
|
|
an existing property. This is effectively the same as modifying the body of a
|
|
setter.
|
|
- Removing any non-public, non-versioned members, including stored properties.
|
|
- Adding a new protocol conformance (with proper availability annotations).
|
|
- Removing conformances to non-public protocols.
|
|
|
|
Omitted from this list is the free addition of new members. Here classes are a
|
|
little more restrictive than structs; they only allow the following changes:
|
|
|
|
- Adding a new convenience initializer.
|
|
- Adding a new designated initializer, if the class is not ``open``.
|
|
- Adding a deinitializer.
|
|
- Adding new, non-overriding method, subscript, or property.
|
|
- Adding a new overriding member, though if the class is ``open`` the type of
|
|
the member may not deviate from the member it overrides. Changing the type
|
|
could be incompatible with existing overrides in subclasses.
|
|
|
|
Finally, classes allow the following changes that do not apply to structs:
|
|
|
|
- A public class may be made ``open`` if it is not already marked ``final``.
|
|
- A non-``open`` public class may be marked ``final``.
|
|
- Removing an explicit deinitializer. (A class with no declared deinitializer
|
|
effectively has an implicit deinitializer.)
|
|
- "Moving" a method, subscript, or property up to its superclass. The
|
|
declaration of the original member must remain along with its original
|
|
availability, but its body may consist of simply calling the new superclass
|
|
implementation.
|
|
- A non-final override of a method, subscript, property, or initializer may be
|
|
removed as long as the generic parameters, formal parameters, and return type
|
|
*exactly* match the overridden declaration. Any existing callers should
|
|
automatically use the superclass implementation.
|
|
- Within an ``open`` class, any public method, subscript, or property may be
|
|
marked ``open`` if it is not already marked ``final``.
|
|
- Any public method, subscript, or property may be marked ``final`` if it is not
|
|
already marked ``open``.
|
|
- ``@IBOutlet``, ``@IBAction``, ``@IBInspectable``, and ``@GKInspectable`` may
|
|
be added to a member without providing any extra version information.
|
|
Removing any of these is a `binary-compatible source-breaking change` if the
|
|
member remains ``@objc``, and disallowed if not.
|
|
- Likewise, ``@IBDesignable`` may be added to a class without providing any
|
|
extra version information. Removing it is considered a `binary-compatible
|
|
source-breaking change`.
|
|
- Changing a class's superclass ``A`` to another class ``B``, *if* class ``B``
|
|
is a subclass of ``A`` *and* class ``B``, along with any superclasses between
|
|
it and class ``A``, were introduced in the latest version of the library.
|
|
|
|
.. admonition:: TODO
|
|
|
|
This last is very tricky to get right. We've seen it happen a few times in
|
|
Apple's SDKs, but at least one of them, `NSCollectionViewItem`_ becoming a
|
|
subclass of NSViewController instead of the root class NSObject, doesn't
|
|
strictly follow the rules. While NSViewController was introduced in the
|
|
same version of the OS, its superclass, NSResponder, was already present.
|
|
If a client app was deploying to an earlier version of the OS, would
|
|
NSCollectionViewItem be a subclass of NSResponder or not? How would the
|
|
compiler be able to enforce this?
|
|
|
|
.. admonition:: TODO
|
|
|
|
Both ``final`` and ``open`` may be applied to a declaration after it has
|
|
been made public. However, these need to be treated as
|
|
`versioned attributes <versioned attribute>`. It's not clear what syntax
|
|
should be used for this.
|
|
|
|
.. _NSCollectionViewItem: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSCollectionViewItem_Class/index.html
|
|
|
|
Other than those detailed above, no other changes to a class or its members
|
|
are permitted. In particular:
|
|
|
|
- An ``open`` class or member cannot become non-``open``.
|
|
- ``final`` may not be removed from a class or its members. (The presence of
|
|
``final`` enables optimization.)
|
|
- ``dynamic`` may not be added to *or* removed from any members. Existing
|
|
clients would not know to invoke the member dynamically.
|
|
- A ``final`` override of a member may *not* be removed, even if the type
|
|
matches exactly; existing clients may be performing a direct call to the
|
|
implementation instead of using dynamic dispatch.
|
|
- ``@objc`` and ``@nonobjc`` may not be added to or removed from the class or
|
|
any existing members.
|
|
- ``@NSManaged`` may not be added to or removed from any existing members.
|
|
|
|
.. admonition:: TODO
|
|
|
|
The ``@NSManaged`` attribute as it is in Swift 4 exposes implementation
|
|
details to clients in a bad way. We need to fix this.
|
|
rdar://problem/20829214
|
|
|
|
|
|
Initializers
|
|
------------
|
|
|
|
New designated initializers may not be added to an ``open`` class. This would
|
|
change the inheritance of convenience initializers, which existing subclasses
|
|
may depend on. An ``open`` class also may not change a convenience initializer
|
|
into a designated initializer or vice versa.
|
|
|
|
A new ``required`` initializer may be added to a class only if it is a
|
|
convenience initializer; that initializer may only call existing ``required``
|
|
initializers. An existing initializer may not be marked ``required``.
|
|
|
|
All of the modifications permitted for top-level functions are also permitted
|
|
for class initializers. Convenience initializers may be marked ``@inlineable``,
|
|
with the same restrictions as top-level functions; designated initializers may
|
|
not.
|
|
|
|
|
|
Methods
|
|
-------
|
|
|
|
Both class and instance methods allow all of the modifications permitted for
|
|
top-level functions, but the potential for overrides complicates things a
|
|
little. They allow the following changes:
|
|
|
|
- Changing the body of the method.
|
|
- Changing *internal* parameter names (i.e. the names used within the method
|
|
body, not the labels that are part of the method's full name).
|
|
- Reordering generic requirements (but not the generic parameters themselves).
|
|
- Adding a default argument expression to a parameter.
|
|
- Changing or removing a default argument is a `binary-compatible
|
|
source-breaking change`.
|
|
- The ``@discardableResult`` and ``@warn_unqualified_access`` attributes may
|
|
be added to a method without any additional versioning information.
|
|
|
|
Class and instance methods may be marked ``@inlineable``, with the same
|
|
restrictions as struct methods. Additionally, only non-overriding ``final``
|
|
methods may be marked ``@inlineable``.
|
|
|
|
.. note::
|
|
|
|
A previous draft of this document allowed non-``final`` methods to be
|
|
marked ``@inlineable``, permitting inlining based on speculative
|
|
devirtualization. This was removed both because of the added complexity for
|
|
users and because it would make methods on classes different from
|
|
struct/enum methods and top-level functions: a method on a class would be
|
|
part of the library's module interface even if it had been inlineable since
|
|
its introduction.
|
|
|
|
|
|
Properties
|
|
----------
|
|
|
|
Class and instance properties allow *most* of the modifications permitted for
|
|
struct properties, but the potential for overrides complicates things a little.
|
|
Variable properties (those declared with ``var``) allow the following changes:
|
|
|
|
- Adding (but not removing) a computed setter to a non-``open`` property.
|
|
- Adding or removing a non-public, non-versioned setter.
|
|
- Changing from a stored property to a computed property, or vice versa, as
|
|
long as a previously versioned setter is not removed.
|
|
- Changing the body of an accessor.
|
|
- Adding or removing an observing accessor (``willSet`` or ``didSet``) to/from
|
|
an existing variable. This is effectively the same as modifying the body of a
|
|
setter.
|
|
- Adding, removing, or changing the initial value of a stored variable.
|
|
- Adding or removing ``weak`` from a variable with ``Optional`` type.
|
|
- Adding or removing ``unowned`` from a variable.
|
|
- Adding or removing ``@NSCopying`` to/from a variable.
|
|
|
|
Adding a public setter to an ``open`` property is a
|
|
`binary-compatible source-breaking change`; any existing overrides will not
|
|
know what to do with the setter and will likely not behave correctly.
|
|
|
|
Constant properties (those declared with ``let``) still permit changing their
|
|
value, as well as adding or removing an initial value entirely.
|
|
|
|
Non-overriding ``final`` variable and constant properties (on both instances
|
|
and classes) may be marked ``@inlineable``. This behaves as described for
|
|
struct properties.
|
|
|
|
|
|
Subscripts
|
|
----------
|
|
|
|
Subscripts behave much like properties; they inherit the rules of their struct
|
|
counterparts with a few small changes:
|
|
|
|
- Adding (but not removing) a public setter to a non-``open`` subscript is
|
|
permitted.
|
|
- Adding or removing a non-public, non-versioned setter is permitted.
|
|
- Changing the body of an accessor is permitted.
|
|
- Changing index parameter internal names is permitted.
|
|
- Reordering generic requirements (but not the generic parameters themselves)
|
|
is permitted.
|
|
- Adding a default argument expression to an index parameter is permitted.
|
|
- Changing or removing a default argument is a `binary-compatible
|
|
source-breaking change`.
|
|
|
|
Adding a public setter to an ``open`` subscript is a
|
|
`binary-compatible source-breaking change`; any existing overrides will not
|
|
know what to do with the setter and will likely not behave correctly.
|
|
|
|
Non-overriding ``final`` class subscripts may be marked ``@inlineable``, which
|
|
behaves as described for struct subscripts.
|
|
|
|
|
|
Possible Restrictions on Classes
|
|
--------------------------------
|
|
|
|
In addition to ``final``, it may be useful to restrict the stored properties of
|
|
a class instance, like `Fixed-Contents Structs`_. However, there are open
|
|
questions about how this would actually work, and the compiler still wouldn't
|
|
be able to make much use of the information, because classes from other
|
|
libraries must almost always be allocated on the heap.
|
|
|
|
The design of this annotation is not covered by this document. As a purely
|
|
additive feature, it can be added to the model at any time.
|
|
|
|
|
|
Extensions
|
|
~~~~~~~~~~
|
|
|
|
Non-protocol extensions largely follow the same rules as the types they extend.
|
|
The following changes are permitted:
|
|
|
|
- Adding new extensions and removing empty extensions (that is, extensions that
|
|
declare neither members nor protocol conformances).
|
|
- Moving a member from one extension to another within the same module, as long
|
|
as both extensions have the exact same constraints.
|
|
- Moving a member from an unconstrained extension to the declaration of the
|
|
base type, provided that the declaration is in the same module. The reverse
|
|
is permitted for all members except stored properties, although note that
|
|
moving all initializers out of a type declaration may cause a new one to be
|
|
implicitly synthesized.
|
|
- Adding a new protocol conformance (with proper availability annotations).
|
|
- Removing conformances to non-public protocols.
|
|
|
|
Adding, removing, reordering, and modifying members follow the same rules as
|
|
the base type; see the sections on structs, enums, and classes above.
|
|
|
|
|
|
Protocol Extensions
|
|
-------------------
|
|
|
|
Protocol extensions follow slightly different rules from other extensions; the
|
|
following changes are permitted:
|
|
|
|
- Adding new extensions and removing empty extensions.
|
|
- Moving a member from one extension to another within the same module, as long
|
|
as both extensions have the exact same constraints.
|
|
- Adding any new member.
|
|
- Reordering members.
|
|
- Removing any non-public, non-versioned member.
|
|
- Changing the body of any methods, initializers, or accessors.
|
|
|
|
.. note::
|
|
|
|
Although it is not related to evolution, it is worth noting that members of
|
|
protocol extensions that do *not* satisfy protocol requirements are not
|
|
overridable, even when the conforming type is a class.
|
|
|
|
|
|
Operators
|
|
~~~~~~~~~
|
|
|
|
Operator declarations are entirely compile-time constructs, so changing them
|
|
does not have any affect on binary compatibility. However, they do affect
|
|
*source* compatibility, so it is recommended that existing operators are not
|
|
changed at all except for the following:
|
|
|
|
- Making a non-associative operator left- or right-associative.
|
|
|
|
Any other change counts as a `binary-compatible source-breaking change`.
|
|
|
|
Operator declarations are not versioned.
|
|
|
|
|
|
Typealiases
|
|
~~~~~~~~~~~
|
|
|
|
Public typealiases within structs, enums, and protocols may be used for
|
|
protocol conformances (to satisfy associated type requirements), not only
|
|
within the library but within client modules as well. Therefore, changing a
|
|
member typealias in any way is not permitted; while it will not break existing
|
|
clients, they cannot recompile their code and get correct behavior.
|
|
|
|
Top-level typealiases only exist at compile-time, so changing the underlying
|
|
type of one is a `binary-compatible source-breaking change`. However, if the
|
|
typealias is *used* in the type of any `versioned entity` in a library, it
|
|
may be an actual breaking change and would not be permitted.
|
|
|
|
It is always permitted to change the *use* of a public typealias to its
|
|
underlying type, and vice versa, at any location in the program.
|
|
|
|
Neither top-level nor member typealiases are versioned.
|
|
|
|
|
|
A Unifying Theme
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
So far this document has talked about ways to give up flexibility for several
|
|
different kinds of declarations: ``@inlineable`` for functions,
|
|
``@fixedContents`` for structs, etc. Each of these has a different set of
|
|
constraints it enforces on the library author and promises it makes to clients.
|
|
However, they all follow a common theme of giving up the flexibility of future
|
|
changes in exchange for improved performance and perhaps some semantic
|
|
guarantees. Therefore, all of these attributes are informally referred to as
|
|
"fragility attributes".
|
|
|
|
Given that these attributes share several characteristics, we could consider
|
|
converging on a single common attribute, say ``@fixed``, ``@inline``, or
|
|
``@fragile``. However, this may be problematic if the same declaration has
|
|
multiple kinds of flexibility.
|
|
|
|
|
|
Versioning Internal Declarations
|
|
================================
|
|
|
|
The initial discussion on versioning focused on public APIs, making sure
|
|
that a client knows what features they can use when a specific version of a
|
|
library is present. Inlineable functions have much the same constraints, except
|
|
the inlineable function is the client and the entities being used may not be
|
|
public.
|
|
|
|
Adding a versioning annotation to an ``internal`` entity promises that the
|
|
entity will be available at link time in the containing module's binary. This
|
|
makes it safe to refer to such an entity from an inlineable function. If the
|
|
entity is ever made ``public`` or ``open``, its availability should not be
|
|
changed; not only is it safe for new clients to rely on it, but *existing*
|
|
clients require its presence as well.
|
|
|
|
.. note::
|
|
|
|
Why isn't this a special form of ``public``? Because we don't want it to
|
|
imply everything that ``public`` does, such as requiring overrides to be
|
|
``public``.
|
|
|
|
Because a versioned class member may eventually be made ``open``, it must be
|
|
assumed that new overrides may eventually appear from outside the module if the
|
|
class is marked ``open`` unless the member is marked ``final``.
|
|
|
|
Non-public conformances are never considered versioned, even if both the
|
|
conforming type and the protocol are versioned. A conformance is considered
|
|
public if and only if both the conforming type and protocol are public.
|
|
|
|
Entities declared ``private`` or ``fileprivate`` may not be versioned; the
|
|
mangled name of such an entity includes an identifier based on the containing
|
|
file, which means moving the declaration to another file changes the entity's
|
|
mangled name. This implies that a client would not be able to find the entity
|
|
at run time if the source code is reorganized, which is unacceptable.
|
|
|
|
.. note::
|
|
|
|
There are ways around this limitation, the most simple being that versioned
|
|
``private`` entities are subject to the same cross-file redeclaration rules
|
|
as ``internal`` entities. However, this is a purely additive feature, so to
|
|
keep things simple we'll stick with the basics.
|
|
|
|
We could do away with the entire feature if we restricted inlineable functions
|
|
and fixed-contents structs to only refer to public entities. However, this
|
|
removes one of the primary reasons to make something inlineable: to allow
|
|
efficient access to a type while still protecting its invariants.
|
|
|
|
|
|
"Backdating"
|
|
============
|
|
|
|
*Backdating* refers to releasing a new version of a library that contains
|
|
changes, but pretending those changes were made in a previous version of the
|
|
library. For example, you might want to release version 1.2 of the "Magician"
|
|
library, but pretend that the "SpellIncantation" struct was fixed-contents
|
|
since its introduction in version 1.0.
|
|
|
|
**This is not safe.**
|
|
|
|
Backdating the availability a versioned entity that was previously non-public
|
|
is clearly not safe: older versions of the library will not expose the entity
|
|
as part of their ABI. What may be less obvious is that the fragility attributes
|
|
likewise are not safe to backdate, even if you know the attributes could have
|
|
been added in the past. To give one example, the presence of ``@closed`` or
|
|
``@fixedContents`` may affect the layout and calling conventions for an enum
|
|
or struct.
|
|
|
|
.. note::
|
|
|
|
If we add an "SPI" feature, such that the use of specific public entities
|
|
is limited to certain clients, it *will* be safe to change the set of
|
|
clients, or remove the restriction altogether. In fact, in such cases the
|
|
library author is *required* to *not* change the availability info that was
|
|
originally presented for the limited set of clients, since as mentioned
|
|
above this may affect how those existing clients use the entities declared
|
|
in the library.
|
|
|
|
|
|
Optimization
|
|
============
|
|
|
|
Allowing a library to evolve inhibits the optimization of client code in
|
|
several ways. For example:
|
|
|
|
- A function that currently does not access global memory might do so in the
|
|
future, so calls to it cannot be freely reordered in client code.
|
|
|
|
- A stored property may be replaced by a computed property in the future, so
|
|
client code must not try to access the storage directly.
|
|
|
|
- A struct may have additional members in the future, so client code must not
|
|
assume it fits in any fixed-sized allocation.
|
|
|
|
In order to make sure client code doesn't make unsafe assumptions, queries
|
|
about properties that may change between library versions must be parameterized
|
|
with the `availability context` that is using the entity. An availability
|
|
context is a set of minimum platform and library versions that can be assumed
|
|
present for code executing within the context. (See `Declaring Library Version
|
|
Dependencies`_.) This allows the compiler to answer the question, "Given what I
|
|
know about where this code will be executed, what can I assume about a
|
|
particular entity being used?".
|
|
|
|
If the entity is declared within the same module as the code that's using it,
|
|
then the code is permitted to know all the details of how the entity is
|
|
declared. After all, if the entity is changed, the code that's using it will be
|
|
recompiled.
|
|
|
|
However, if the entity is declared in another module, then the code using it
|
|
must be more conservative, and will therefore receive more conservative answers
|
|
to its queries. For example, a stored property may report itself as computed.
|
|
|
|
The presence of versioned fragility attributes makes the situation more
|
|
complicated. Within a client function that requires version 1.5 of a particular
|
|
library, the compiler should be able to take advantage of any fragility
|
|
information (and performance assertions) introduced prior to version 1.5.
|
|
|
|
|
|
Inlineable Code
|
|
~~~~~~~~~~~~~~~
|
|
|
|
By default, the availability context for a library always includes the latest
|
|
version of the library itself, since that code is always distributed as a unit.
|
|
However, this is not true for functions that have been marked inlineable (see
|
|
`Inlineable Functions`_ above). Inlineable code must be treated as if it is
|
|
outside the current module, since once it's inlined it will be.
|
|
|
|
For inlineable code, the availability context is exactly the same as the
|
|
equivalent non-inlineable code except that the assumed version of the
|
|
containing library is the version attached to the ``@inlineable`` attribute, or
|
|
the version of the library in which the entity was introduced, and any `library
|
|
version dependencies <#declaring-library-version-dependencies>`_ or minimum
|
|
deployment target must be specified explicitly using ``@available``. Code
|
|
within this context must be treated as if the containing library were just a
|
|
normal dependency.
|
|
|
|
A versioned inlineable function still has an exported symbol in the library
|
|
binary, which may be used when the function is referenced from a client rather
|
|
than called. This version of the function is not subject to the same
|
|
restrictions as the version that may be inlined, and so it may be desirable to
|
|
compile a function twice: once for inlining, once for maximum performance.
|
|
|
|
If the body of an inlineable function is used in any way by a client module
|
|
(say, to determine that it does not read any global variables), that module
|
|
must take care to emit and use its own copy of the function. This is because
|
|
analysis of the function body may not apply to the version of the function
|
|
currently in the library.
|
|
|
|
|
|
Local Availability Contexts
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Swift availability contexts aren't just at the declaration level; they also
|
|
cover specific regions of code inside function bodies as well. These "local"
|
|
constructs are formed using the ``#available`` construct, which performs a
|
|
dynamic check.
|
|
|
|
In theory, it would be legal to allow code dominated by a ``#available`` check
|
|
to take advantage of additional fragility information introduced by the more
|
|
restrictive dependencies that were checked for. However, this is an additional
|
|
optimization that may be complicated to implement (and even to represent
|
|
properly in SIL), and so it is not a first priority.
|
|
|
|
|
|
Other Promises About Types
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Advanced users may want to promise more specific things about various types.
|
|
These are similar to the internal ``effects`` attribute we have for functions,
|
|
except that they can be enforced by the compiler.
|
|
|
|
- ``trivial``: Promises that assignment just requires a fixed-size bit-for-bit
|
|
copy without any indirection or reference-counting operations.
|
|
|
|
- ``maximumFootprint(sizeInBits: N, alignmentInBits: A)``: Promises that the
|
|
type's size and required alignment are at most N bits and A bits,
|
|
respectively. (Both may be smaller.)
|
|
|
|
- ``fixedSize``: Promises that the type has *some* size known at compile-time,
|
|
allowing optimizations like promoting allocations to the stack. Only applies
|
|
to fixed-contents structs and closed enums, which can already infer this
|
|
information; the explicit annotation allows it to be enforced.
|
|
|
|
Collectively these features are known as "performance assertions", to
|
|
underscore the fact that they do not affect how a type is used at the source
|
|
level, but do allow for additional optimizations. We may also expose some of
|
|
these qualities to static or dynamic queries for performance-sensitive code.
|
|
|
|
.. note:: Previous revisions of this document contained a ``noPayload``
|
|
assertion for enums. However, this doesn't actually offer any additional
|
|
optimization opportunities over combining ``trivial`` with
|
|
``maximumFootprint``, and the latter is more flexible.
|
|
|
|
.. note:: None of these names / spellings are final. The name "trivial" comes
|
|
from C++, though Swift's trivial is closer to C++'s "`trivially copyable`__".
|
|
|
|
All of these features need to be versioned, just like the more semantic
|
|
fragility attributes above. The exact spelling is not proposed by this document.
|
|
|
|
__ http://en.cppreference.com/w/cpp/types/is_trivially_copyable
|
|
|
|
|
|
Resilience Domains
|
|
==================
|
|
|
|
As described in the `Introduction`_, the features and considerations discussed
|
|
in this document do not apply to libraries distributed in a bundle with their
|
|
clients. In this case, a client can rely on all the current implementation
|
|
details of its libraries when compiling, since the same version of the library
|
|
is guaranteed to be present at runtime. This allows more optimization than
|
|
would otherwise be possible.
|
|
|
|
In some cases, a collection of libraries may be built and delivered together,
|
|
even though their clients may be packaged separately. (For example, the ICU
|
|
project is usually built into several library binaries, but these libraries are
|
|
always distributed together.) While the *clients* cannot rely on a particular
|
|
version of any library being present, the various libraries in the collection
|
|
should be able to take advantage of the implementations of their dependencies
|
|
also in the collection---that is, it should treat all entities as if marked
|
|
with the appropriate fragility attributes. Modules in this sort of collection
|
|
are said to be in the same *resilience domain.*
|
|
|
|
Exactly how resilience domains are specified is not covered by this document,
|
|
and indeed they are an additive feature. One possibility is that a library's
|
|
resilience domain defaults to the name of the module, but can be overridden. If
|
|
a client has the same resilience domain name as a library it is using, it may
|
|
assume that version of the library will be present at runtime.
|
|
|
|
|
|
Deployments
|
|
~~~~~~~~~~~
|
|
|
|
Related to the concept of a resilience domain is a *deployment.* While a
|
|
resilience domain allows related libraries to be compiled more efficiently,
|
|
a deployment groups related libraries together to present semantic version
|
|
information to clients. The simplest example of this might be an OS release:
|
|
OS X 10.10.0 contains Foundation version 1151.16 and AppKit version 1343. A
|
|
deployment thus acts as a "virtual dependency": clients that depend on
|
|
OS X 10.10 can rely on the presence of both of the library versions above.
|
|
|
|
The use of deployments allows clients to only have to think about aggregate
|
|
dependencies, instead of listing every library they might depend on. It also
|
|
allows library authors to build `many versions of a library`__ within a larger
|
|
release cycle, as well as allowing a vendor to bundle together many libraries
|
|
with uncoordinated release schedules and release them as a logical unit.
|
|
|
|
__ https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Constants/index.html#//apple_ref/doc/constant_group/Foundation_Framework_Version_Numbers
|
|
|
|
There are lots of details to figure out here, including how to distribute this
|
|
information. In particular, just like libraries publish the history of their
|
|
own APIs, a deployment must publish the history of their included library
|
|
versions, i.e. not just that OS X 10.10 contains Foundation 1151.16 and AppKit
|
|
1343, but also that OS X 10.9 contains Foundation 1056 and AppKit 1265, and that
|
|
OS X 10.8 contains Foundation 945.0 and AppKit 1187, and so on, back to the
|
|
earliest version of the deployment that is supported.
|
|
|
|
|
|
|
|
Checking Binary Compatibility
|
|
=============================
|
|
|
|
With this many manual controls, it's important that library owners be able to
|
|
check their work. Therefore, we intend to build a tool that can compare two
|
|
versions of a library's public interface, and present any suspect differences
|
|
for verification. Important cases include but are not limited to:
|
|
|
|
- Removal of versioned entities.
|
|
|
|
- Incompatible modifications to versioned entities, such as added protocol
|
|
conformances lacking versioning information.
|
|
|
|
- Unsafe `backdating <#backdating>`_.
|
|
|
|
- Unsafe modifications to entities marked with fragility attributes, such as
|
|
adding a stored property to a ``@fixedContents`` struct.
|
|
|
|
Wherever possible, this tool should also check for `binary-compatible
|
|
source-breaking changes <binary-compatible source-breaking change>`, such as
|
|
changing a default argument from ``false`` to ``true``.
|
|
|
|
|
|
Automatic Versioning
|
|
~~~~~~~~~~~~~~~~~~~~
|
|
|
|
A possible extension of this "checker" would be a tool that *automatically*
|
|
generates versioning information for entities in a library, given the previous
|
|
public interface of the library. This would remove the need for versions on any
|
|
of the fragility attributes, and declaring versioned API would be as simple as
|
|
marking an entity ``public``. Obviously this would also remove the possibility
|
|
of human error in managing library versions.
|
|
|
|
However, making this tool has a number of additional difficulties beyond the
|
|
simple checker tool:
|
|
|
|
- The tool must be able to read past library interface formats. This is true
|
|
for a validation tool as well, but the cost of failure is much higher.
|
|
Similarly, the past version of a library *must* be available to correctly
|
|
compile a new version.
|
|
|
|
- Because the information goes into a library's public interface, the
|
|
versioning tool must either be part of the compilation process, modify the
|
|
interface generated by compilation, or produce a sidecar file that can be
|
|
loaded when compiling the client. In any case, it must *produce* information
|
|
in addition to *consuming* it.
|
|
|
|
- Occasionally a library owner may want to override the inferred versions. This
|
|
can be accomplished by providing explicit versioning information, as
|
|
described above.
|
|
|
|
- Bugs in the tool manifest as bugs in client programs.
|
|
|
|
Because this tool would require a fair amount of additional work, it is not
|
|
part of this initial model. It is something we may decide to add in the future.
|
|
|
|
|
|
Open Issues
|
|
===========
|
|
|
|
There are still a number of known issues with the model described in this
|
|
document. We should endeavor to account for each of them, and if we can't come
|
|
up with a satisfactory implementation we should at least make sure that they
|
|
will not turn into pitfalls for library or client developers.
|
|
|
|
|
|
Subclass and base both conform to protocol
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
::
|
|
|
|
// Library, version 1
|
|
class Elf {}
|
|
protocol Summonable {}
|
|
|
|
::
|
|
|
|
// Client, version 1
|
|
class ShoemakingElf : Elf, Summonable {}
|
|
|
|
::
|
|
|
|
// Library, version 2
|
|
@available(2.0)
|
|
extension Elf : Summonable {}
|
|
|
|
Now ``ShoemakingElf`` conforms to ``Summonable`` in two different ways, which
|
|
may be incompatible (especially if ``Summonable`` had associated types or
|
|
requirements involving ``Self``).
|
|
|
|
Additionally, the client can't even remove ``ShoemakingElf``'s conformance to
|
|
``Summonable``, because it may itself be a library with other code depending on
|
|
it. We could fix that with an annotation to explicitly inherent the conformance
|
|
of ``Summonable`` from the base class, but even that may not be possible if
|
|
there are incompatible associated types involved (because changing a member
|
|
typealias is not a safe change).
|
|
|
|
One solution is to disallow adding a conformance for an existing protocol to an
|
|
``open`` class.
|
|
|
|
|
|
Recompiling changes a protocol's implementation
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
::
|
|
|
|
// Library, version 1
|
|
protocol MagicType {}
|
|
protocol Wearable {}
|
|
func use<T: MagicType>(_ item: T) {}
|
|
|
|
::
|
|
|
|
// Client, version 1
|
|
struct Amulet : MagicType, Wearable {}
|
|
use(Amulet())
|
|
|
|
::
|
|
|
|
// Library, version 2
|
|
protocol MagicType {
|
|
@available(2.0)
|
|
func equip() { print("Equipped.") }
|
|
}
|
|
|
|
extension Wearable where Self: MagicType {
|
|
@available(2.0)
|
|
func equip() { print("You put it on.") }
|
|
}
|
|
|
|
func use<T: MagicType>(_ item: T) { item.equip() }
|
|
|
|
Before the client is recompiled, the implementation of ``equip()`` used for
|
|
``Amulet`` instances can only be the default implementation, i.e. the one that
|
|
prints "Equipped". However, recompiling the client will result in the
|
|
constrained implementation being considered a "better" match for the protocol
|
|
requirement, thus changing the behavior of the program.
|
|
|
|
This should never change the *meaning* of a program, since the default
|
|
implementation for a newly-added requirement should always be *correct.*
|
|
However, it may have significantly different performance characteristics or
|
|
side effects that would make the difference in behavior a surprise.
|
|
|
|
This is similar to adding a new overload to an existing set of functions, which
|
|
can also change the meaning of client code just by recompiling. However, the
|
|
difference here is that the before-recompilation behavior was never requested
|
|
or acknowledged by the client; it's just the best the library can do.
|
|
|
|
A possible solution here is to require the client to acknowledge the added
|
|
requirement in some way when it is recompiled.
|
|
|
|
(We do not want to perform overload resolution at run time to find the best
|
|
possible default implementation for a given type.)
|
|
|
|
|
|
Summary
|
|
=======
|
|
|
|
When possible, Swift gives library authors freedom to evolve their code
|
|
without breaking binary compatibility. This has implications for both the
|
|
semantics and performance of client code, and so library owners also have tools
|
|
to waive the ability to make certain future changes. The language guarantees
|
|
that client code will never accidentally introduce implicit dependencies on
|
|
specific versions of libraries.
|
|
|
|
|
|
Related Proposals
|
|
=================
|
|
|
|
The following proposals (some currently in the process, some planned) will
|
|
affect the model described in this document, or concern the parts of this
|
|
document that affect language semantics:
|
|
|
|
- (draft) `Overridable methods in extensions`_
|
|
- (planned) Restricting retroactive modeling (protocol conformances for types you don't own)
|
|
- (planned) Default implementations in protocols
|
|
- (planned) `Generalized existentials (values of protocol type) <Generics>`_
|
|
- (planned) Open and closed enums
|
|
- (planned) Removing the "constant" guarantee for 'let' across module boundaries
|
|
- (planned) Syntax for declaring "versioned" entities and their features
|
|
- (planned) Syntax for declaring inlineable code
|
|
- (planned) Syntax for declaring fixed-contents structs
|
|
- (?) Non-inherited protocol conformances
|
|
- (future) Performance annotations for types
|
|
- (future) Attributes for stored property accessors
|
|
- (future) Stored properties in extensions
|
|
|
|
.. _Overridable methods in extensions: https://github.com/jrose-apple/swift-evolution/blob/overridable-members-in-extensions/proposals/nnnn-overridable-members-in-extensions.md
|
|
.. _Generics: https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#generalized-existentials
|
|
|
|
This does not mean all of these proposals need to be accepted, only that their
|
|
acceptance or rejection will affect this document.
|
|
|
|
|
|
Glossary
|
|
========
|
|
|
|
.. glossary::
|
|
|
|
ABI
|
|
The runtime contract for using a particular API (or for an entire library),
|
|
including things like symbol names, calling conventions, and type layout
|
|
information. Stands for "Application Binary Interface".
|
|
|
|
API
|
|
An `entity` in a library that a `client` may use, or the collection of all
|
|
such entities in a library. (If contrasting with `SPI`, only those entities
|
|
that are available to arbitrary clients.) Marked ``public`` or ``open`` in
|
|
Swift. Stands for "Application Programming Interface".
|
|
|
|
availability context
|
|
The collection of library and platform versions that can be assumed, at
|
|
minimum, to be present in a certain block of code. Availability contexts
|
|
are always properly nested, and the global availability context includes
|
|
the module's minimum deployment target and minimum dependency versions.
|
|
|
|
backwards-compatible
|
|
A modification to an API that does not break existing clients. May also
|
|
describe the API in question.
|
|
|
|
binary compatibility
|
|
A general term encompassing both backwards- and forwards-compatibility
|
|
concerns. Also known as "ABI compatibility".
|
|
|
|
binary-compatible source-breaking change
|
|
A change that does not break `binary compatibility`, but which is known to
|
|
either change the behavior of existing clients or potentially result in
|
|
errors when a client is recompiled. In most cases, a client that *hasn't*
|
|
been recompiled may use the new behavior or the old behavior, or even a
|
|
mix of both; however, this will always be deterministic (same behavior when
|
|
a program is re-run) and will not break Swift's memory-safety and
|
|
type-safety guarantees. It is recommended that these kinds of changes are
|
|
avoided just like those that break binary compatibility.
|
|
|
|
client
|
|
A target that depends on a particular library. It's usually easiest to
|
|
think of this as an application, but it could be another library.
|
|
(In certain cases, the "library" is itself an application, such as when
|
|
using Xcode's unit testing support.)
|
|
|
|
duck typing
|
|
In Objective-C, the ability to treat a class instance as having an
|
|
unrelated type, as long as the instance handles all messages sent to it.
|
|
(Note that this is a dynamic constraint.)
|
|
|
|
entity
|
|
A type, function, member, or global in a Swift program. Occasionally the
|
|
term "entities" also includes conformances, since these have a runtime
|
|
presence and are depended on by clients.
|
|
|
|
forwards-compatible
|
|
An API that is designed to handle future clients, perhaps allowing certain
|
|
changes to be made without changing the ABI.
|
|
|
|
fragility attribute
|
|
See `A Unifying Theme`_.
|
|
|
|
module
|
|
The primary unit of code sharing in Swift. Code in a module is always built
|
|
together, though it may be spread across several source files.
|
|
|
|
performance assertion
|
|
See `Other Promises About Types`_.
|
|
|
|
resilience domain
|
|
A grouping for code that will always be recompiled and distributed
|
|
together, and can thus take advantage of details about a type
|
|
even if it changes in the future.
|
|
|
|
SPI
|
|
A subset of `API` that is only available to certain clients. Stands for
|
|
"System Programming Interface".
|
|
|
|
target
|
|
In this document, a collection of code in a single Swift module that is
|
|
built together; a "compilation unit". Roughly equivalent to a target in
|
|
Xcode.
|
|
|
|
versioned entity
|
|
See `Publishing Versioned API`_.
|
|
|
|
versioned attribute
|
|
See `Publishing Versioned API`_.
|