| :orphan: |
| |
| .. default-role:: term |
| .. title:: Library Evolution Support in Swift ("Resilience") |
| |
| :Author: Jordan Rose |
| :Author: John McCall |
| |
| 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: |
| |
| * 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. |
| |
| * Avoiding unnecessary language guarantees and taking advantage of that |
| flexibility to limit load-time costs. |
| |
| We also intend to provide tools to detect inadvertent changes in interfaces. |
| |
| .. contents:: :local: |
| |
| .. warning:: **This document is still in draft stages.** Large additions and |
| restructuring are still planned, including: |
| |
| * A proper definition for "versioned entity". |
| * Several possible versioned attribute syntaxes, instead of just this one. |
| * A discussion of back-dating, and how it usually is not allowed. |
| * A brief discussion of the implementation issues for fixed-layout value types with resilient members, and with non-public members. |
| * A revisal of the discussion on fixed-layout classes. |
| * A brief discussion of "deployment files", which represent distribution groupings that are themselves versioned. (For example, OS X 10.10.3 contains Foundation version 1153.20.) Deployment files are likely to provide a concrete implementation of "resilience domains". |
| * A way to specify "minimum deployment libraries", like today's minimum deployment targets. |
| |
| 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 model is not of interest to libraries that are bundled with their clients |
| (distribution via source, static library, or embedded/sandboxed dynamic |
| library). 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. |
| |
| 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 having |
| certain APIs from a library must themselves be restricted to contexts in which |
| those APIs are available. This is accomplished using ``@available`` as well, |
| 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) { |
| conjureDemons() |
| } 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. |
| |
| |
| Publishing Versioned API |
| ======================== |
| |
| A library's API is already marked with the ``public`` attribute. Versioning |
| information can be added to any ``public`` entity with the ``@available`` |
| attribute, this time specifying *only* a version number. This declares when the |
| entity was first exposed publicly in the current module. |
| |
| :: |
| |
| @available(1.2) |
| public func conjureDemons() |
| |
| .. admonition:: TODO |
| |
| Should this go on ``public`` instead? How does this play with SPI |
| <rdar://problem/18844229>? |
| |
| Using the same attribute for both publishing and using versioned APIs helps tie |
| the feature together and enforces a consistent set of rules. The one difference |
| is that code within a library may always 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. |
| |
| Swift libraries are strongly encouraged to use `semantic versioning`_, but this |
| is not enforced by the language. |
| |
| Some ``internal`` entities may also use ``@available``. See `Pinning`_ below. |
| |
| .. _semantic versioning: http://semver.org |
| |
| |
| 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 public 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 value to a parameter. |
| - Changing a default value is permitted but discouraged; it changes the meaning |
| of existing source code. |
| |
| .. note:: |
| |
| Today's implementation of default values puts the evaluation of the default |
| value expression in the library, rather than in the client like C++ or C#. |
| This is problematic if we want to allow adding new default values. |
| |
| .. admonition:: TODO |
| |
| Is *removing* a default value something we want to allow? It breaks source |
| compatibility, but not binary compatibility under the inlining model. That |
| said, changing a default value is discouraged, and removing + adding is the |
| same thing. |
| |
| No other changes are permitted; the following are particularly of note: |
| |
| - A public function may not change its parameters or return type. |
| - A public function may not change its generic requirements. |
| - A public function may not change its external parameter names (labels). |
| - A public function may not add, remove, or reorder parameters, whether or not |
| they have default values. |
| |
| .. admonition:: TODO |
| |
| Can a throwing function become non-throwing? It's a "safe" change but |
| it's hard to document how it used to behave for backwards-deployers. |
| |
| |
| 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-layout struct with non-public members; this |
| allows the library author to preserve invariants while still allowing |
| efficient access to the struct. |
| |
| A public function marked with the ``@inlineable`` attribute makes its body |
| available to clients as part of the module's public interface. The |
| ``@inlineable`` attribute takes a version number, just like ``@available``; |
| clients may not assume that the body of the function is suitable when deploying |
| against older versions of the library. |
| |
| Clients are not required to inline a function marked ``@inlineable``. |
| |
| .. 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 may or may not affect existing clients. Existing |
| clients may use the new implementation, or they may use the implementation |
| from the time they were compiled, or they may use both inconsistently. |
| |
| |
| Restrictions on Inlineable Functions |
| ------------------------------------ |
| |
| Because the body of an inlineable function (or method, accessor, initializer, |
| or deinitializer) may 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 distanceTo(other: Point2D) -> Double { |
| let deltaX = self.x - other.x |
| let deltaY = self.y - other.y |
| return sqrt(deltaX*deltaX + deltaY*deltaY) |
| } |
| } |
| |
| As written, this ``distanceTo`` 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, we have |
| the following restrictions on the bodies of inlineable functions: |
| |
| - **They may not define any local types** (other than typealiases). |
| |
| - **They must not reference any** ``private`` **entities,** except for local |
| functions declared within the inlineable function itself. |
| |
| - **They must not reference any** ``internal`` **entities except for those that |
| have been** `availability-pinned`_. See below for a discussion of pinning. |
| |
| - **They must not reference any entities less available than the function |
| itself.** |
| |
| .. _availability-pinned: #pinning |
| |
| An inlineable function is still emitted into its own module's binary. This |
| makes it possible to take an existing function and make it inlineable, as long |
| as the current body makes sense when deploying against an earlier version of |
| the library. |
| |
| 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 Functions |
| --------------- |
| |
| If an inlineable function contains local functions or closures, these are |
| implicitly made inlineable as well. This is important in case the library |
| author decides to change the inlineable function later. If the inlineable |
| function is emitted into a client module as described above, the local |
| functions must be as well. (At the SIL level, these local functions are |
| considered to have ``shared`` linkage.) |
| |
| Local functions are subject to the same restrictions as the inlineable |
| functions containing them, as described above. |
| |
| Pinning |
| ------- |
| |
| FIXME: We're just going to call this "internal but versioned". |
| |
| An `availability-pinned` entity is simply an ``internal`` member, free |
| function, or global binding that has been marked ``@available``. This 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 a pinned entity is ever made ``public``, its availability should |
| not be changed. |
| |
| .. 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 pinned class member may eventually be made public, it must be assumed |
| that new overrides may eventually appear from outside the module unless the |
| member is marked ``final`` or the class is not publicly subclassable. |
| |
| We could do away with the entire "pinning" feature if we restricted inlineable |
| functions 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. |
| |
| .. note:: |
| |
| Types are not allowed to be pinned because that would have many more ripple |
| effects. It's not technically impossible; it just requires a lot more |
| thought. |
| |
| |
| Top-Level Variables and Constants |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| |
| Given a public 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 setter. |
| - Changing from a stored variable to a computed variable, or vice versa, as |
| long as a previously-public 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. |
| |
| .. admonition:: TODO |
| |
| We need to pin down how this interacts with the "Behaviors" proposal. |
| Behaviors that just change the accessors of a global are fine, but those |
| that provide new entry points are trickier. |
| |
| 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. |
| |
| .. note:: We could make it safe to turn a read-only variable into a constant, |
| but currently do not promise that that is a binary-compatible change. |
| |
| |
| 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 public setter to a computed variable is still permitted. |
| - Adding or removing a non-public 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 permitted but discouraged; existing |
| clients may use the new implementations, or they may use the implementations |
| from the time they were compiled, or a mix of both. |
| - Adding/removing observing accessors is likewise permitted but discouraged, |
| for the same reason. |
| - Changing the initial value of a stored variable is still permitted. |
| - Changing the value of a constant is permitted but discouraged; like accessors, |
| existing clients may use the new value, or the value from when they were |
| compiled, or a mix of both. |
| |
| .. 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. |
| |
| 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 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. |
| |
| .. admonition:: TODO |
| |
| We need to pin down how this, and the ``@fixed_layout`` attribute below, |
| interacts with the "Behaviors" proposal. Behaviors that just change the |
| accessors of a property are fine, but those that provide new entry points |
| are trickier. |
| |
| 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. |
| |
| |
| 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 obvious 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 MyStruct : SomeProto {…} |
| |
| and |
| |
| :: |
| |
| extension MyStruct : @available(1.1) SomeProto {…} |
| |
| 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-layout Structs |
| -------------------- |
| |
| To opt out of this flexibility, a struct may be marked ``@fixed_layout``. This |
| promises that no stored properties will be added to or removed from the struct, |
| even ``private`` or ``internal`` ones. In effect: |
| |
| - Reordering stored instance properties relative to one another 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, or accessors is |
| permitted. |
| - Adding or removing observing accessors from public stored properties is still |
| permitted. |
| - Removing stored instance properties is not permitted. Removing any other |
| non-public members is still permitted. |
| - Adding a new protocol conformance is still permitted. |
| - Removing conformances to non-public protocols is still permitted. |
| |
| The ``@fixed_layout`` attribute takes a version number, just like |
| ``@available``. 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 ``@fixed_layout`` attribute |
| were absent.) |
| |
| .. admonition:: TODO |
| |
| We really shouldn't care about the *order* of the stored properties. |
| |
| .. admonition:: TODO |
| |
| Because observing accessors can still be added to public stored properties, |
| we don't get efficient setters unless they are also marked ``@inlineable``. |
| This seems suboptimal. |
| |
| .. admonition:: TODO |
| |
| There are implementation concerns when the non-public fields do not have |
| public types. We probably just want to ban that configuration; otherwise, |
| clients will silently get slow performance copying the struct around. |
| |
| |
| Fixed Properties |
| ---------------- |
| |
| As shown above, the ``@fixed_layout`` attribute promises that all stored |
| properties currently in a type will remain stored in all future library |
| versions, but sometimes that isn't a reasonable promise. In this case, a |
| library owner may still want to allow clients to rely on a *specific* stored |
| property remaining stored, by applying the ``@fixed`` attribute to the property. |
| |
| .. admonition:: TODO |
| |
| Is it valid for a fixed property to have observing accessors, or is it more |
| useful to promise that the setter is just a direct field access too? If it |
| were spelled ``@fragile``, I would assume that accessors are permitted but |
| they become inlineable, and so not having any accessors is just a |
| degenerate case of that. |
| |
| Like all other attributes in this section, the ``@fixed`` attribute must |
| specify in which version of the library clients may rely on the property being |
| stored. The attribute may not be applied to non-final properties in classes. |
| |
| .. note:: |
| |
| It would be possible to allow ``@fixed`` on non-final properties, and have |
| it only apply when the client code is definitively working with an instance |
| of the base class, not any of its subclasses. But this is probably too |
| subtle, and makes it look like the attribute is doing something useful when |
| it actually isn't. |
| |
| .. note:: |
| |
| This is getting into "diminishing returns" territory. Should we just take |
| it out of the document for now? We can probably add it later, although like |
| the other attributes it couldn't be backdated. |
| |
| 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, although this is discouraged. 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 case. |
| - Adding any other members. |
| - Removing any non-public 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. |
| |
| .. admonition:: TODO |
| |
| This states that adding/removing ``indirect`` (on either a case or the |
| entire enum) is considered a breaking change. Is that what we want? |
| |
| |
| Closed Enums |
| ------------ |
| |
| A library owner may opt out of this flexibility by marking the enum as |
| ``@closed``. A "closed" enum may not have any ``private`` or ``internal`` cases |
| 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 still permitted. |
| - Removing a non-public case is not applicable. |
| - Adding any other members is still permitted. |
| - Removing any non-public members is still permitted. |
| - Adding a new protocol conformance is still permitted. |
| - Removing conformances to non-public protocols is still permitted. |
| |
| .. note:: |
| |
| Were a "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. |
| |
| The ``@closed`` attribute takes a version number, just like ``@available``. |
| 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.) |
| |
| 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: |
| |
| - A new non-type requirement may be added to a protocol, as long as it has an |
| unconstrained default implementation. |
| - A new optional requirement may be added to an ``@objc`` protocol. |
| - All members may be reordered, including associated types. |
| |
| However, any members may be added to protocol extensions, and non-public |
| members may always be removed from protocol extensions. |
| |
| .. admonition:: TODO |
| |
| We don't have an implementation model hammered out for adding new |
| defaulted requirements, but it is desirable. |
| |
| .. admonition:: TODO |
| |
| It would also be nice to be able to add new associated types with default |
| values, but that seems trickier to implement. |
| |
| |
| 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 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 publicly |
| subclassable. |
| - Adding a deinitializer. |
| - Adding new, non-overriding method, subscript, or property. |
| - Adding a new overriding member, as long as its type does not change. |
| Changing the type could be incompatible with existing overrides in subclasses. |
| |
| Finally, classes allow the following changes that do not apply to structs: |
| |
| - "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. |
| - 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 |
| |
| The latter 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? |
| |
| .. _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: |
| |
| - New designated initializers may not be added to a publicly-subclassable |
| class. This would change the inheritance of convenience initializers, which |
| existing subclasses may depend on. |
| - New ``required`` initializers may not be added to a publicly-subclassable |
| class. There is no way to guarantee their presence on existing subclasses. |
| - ``final`` may not be added to *or* removed from a class or any of its members. |
| The presence of ``final`` enables optimization; its absence means there may |
| be subclasses/overrides that would be broken by the change. |
| - ``dynamic`` may not be added to *or* removed from any members. Existing |
| clients would not know to invoke the member dynamically. |
| |
| .. note:: This ties in with the ongoing discussions about |
| "``final``-by-default" and "non-publicly-subclassable-by-default". |
| |
| |
| Possible Restrictions on Classes |
| -------------------------------- |
| |
| In addition to ``final``, it may be useful to restrict the size of a class |
| instance (like a struct's ``@fixed_layout``) or the number of overridable |
| members in its virtual dispatch table. These annotations have not been designed. |
| |
| |
| 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. |
| - 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 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, 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; 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 member. |
| - Changing the body of any methods, initializers, or accessors. |
| |
| |
| 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. |
| |
| |
| A Unifying Theme |
| ~~~~~~~~~~~~~~~~ |
| |
| So far this proposal has talked about ways to give up flexibility for several |
| different kinds of declarations: ``@inlineable`` for functions, |
| ``@fixed_layout`` 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, as in the description of classes above. |
| |
| |
| 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. 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. |
| Code within this context must be treated as if the containing library were just |
| a normal dependency. |
| |
| A publicly inlineable function still has a public symbol, 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. |
| |
| |
| 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 the type is `trivial`. |
| |
| - ``size_in_bits(N)``: Promises that the type is not larger than a certain |
| size. (It may be smaller.) |
| |
| 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 ``no_payload`` |
| assertion for enums. However, this doesn't actually offer any additional |
| optimization opportunities over combining ``trivial`` with ``size_in_bits``, |
| and the latter is more flexible. |
| |
| 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. |
| |
| |
| 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 proposal, |
| 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. |
| |
| |
| Checking Binary Compatibility |
| ============================= |
| |
| With this many manual controls, it's important that library owners be able to |
| check their work. Therefore, we intend to ship 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 public entities. |
| |
| - Incompatible modifications to public entities, such as added protocol |
| conformances lacking versioning information. |
| |
| - Unsafely-backdated "fragile" attributes as discussed in the `Giving Up |
| Flexibility`_ section. |
| |
| - Unsafe modifications to entities marked with the "fragile" attributes, such as |
| adding a stored property to a ``@fixed_layout`` struct. |
| |
| |
| 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 in the |
| proposal. |
| |
| - 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. |
| |
| |
| Summary |
| ======= |
| |
| When possible, Swift gives library developers 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. |
| |
| |
| 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`` 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. |
| |
| availability-pinned |
| See `Pinning`_. |
| |
| 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". |
| |
| 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. |
| |
| 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. |
| |
| trivial |
| A value whose assignment just requires a fixed-size bit-for-bit copy |
| without any indirection or reference-counting operations. |