mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
* Fix a documentation build failure caused by missing _ underscore in footnote citation. * Get archive/LangRef.html building again. I'm not sure this is the most desirable way to address the failure to locate archive/LangRef.html but I confirmed this causes the desired _build/html/archive/LangRef.html to be created when making docs.
397 lines
14 KiB
ReStructuredText
397 lines
14 KiB
ReStructuredText
:orphan:
|
|
|
|
Initializer Inheritance
|
|
=======================
|
|
|
|
:Authors: Doug Gregor, John McCall
|
|
|
|
.. contents::
|
|
|
|
Introduction
|
|
------------
|
|
This proposal introduces the notion of initializer inheritance into
|
|
the Swift initialization model. The intent is to more closely model
|
|
Objective-C's initializer inheritance model while maintaining memory
|
|
safety.
|
|
|
|
Background
|
|
----------
|
|
An initializer is a definition, and it belongs to the class that
|
|
defines it. However, it also has a signature, and it makes sense to
|
|
talk about other initializers in related classes that share that
|
|
signature.
|
|
|
|
Initializers come in two basic kinds: complete object and subobject.
|
|
That is, an initializer either takes responsibility for initializing
|
|
the complete object, potentially including derived class subobjects,
|
|
or it takes responsibility for initializing only the subobjects of its
|
|
class and its superclasses.
|
|
|
|
There are three kinds of delegation:
|
|
|
|
* **super**: runs an initializer belonging to a superclass (in ObjC,
|
|
``[super init...]``; in Swift, ``super.init(...)``).
|
|
|
|
* **peer**: runs an initializer belonging to the current class (ObjC
|
|
does not have syntax for this, although it is supported by the
|
|
runtime; in Swift, the current meaning of ``self.init(...)``)
|
|
|
|
* **dispatched**: given a signature, runs the initializer with that
|
|
signature that is either defined or inherited by the most-derived
|
|
class (in ObjC, ``[self init...]``; not currently supported by Swift)
|
|
|
|
We can also distinguish two ways to originally invoke an initializer:
|
|
|
|
* **direct**: the most-derived class is statically known
|
|
|
|
* **indirect**: the most-derived class is not statically known and
|
|
an initialization must be dispatched
|
|
|
|
Either kind of dispatched initialization poses a soundness problem
|
|
because there may not be a sound initializer with any given signature
|
|
in the most-derived class. In ObjC, initializers are normal instance
|
|
methods and are therefore inherited like normal, but this isn't really
|
|
quite right; initialization is different from a normal method in that
|
|
it's not inherently sensible to require subclasses to provide
|
|
initializers at all the signatures that their superclasses provide.
|
|
|
|
Subobject initializers
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
The defining class of a subobject initializer is central to its
|
|
behavior. It can be soundly inherited by a class C only if is trivial
|
|
to initialize the ivars of C, but it's convenient to ignore that and
|
|
assume that subobjects will always trivially wrap and delegate to
|
|
superclass subobject initializers.
|
|
|
|
A subobject initializer must either (1) delegate to a peer subobject
|
|
initializer or (2) take responsibility for initializing all ivars of
|
|
its defining class and delegate to a subobject initializer of its
|
|
superclass. It cannot initialize any ivars of its defining class if
|
|
it then delegates to a peer subobject initializer, although it can
|
|
re-assign ivars after initialization completes.
|
|
|
|
A subobject initializer soundly acts like a complete object
|
|
initializer of a class C if and only if it is defined by C.
|
|
|
|
Complete object initializers
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
The defining class of a complete object initializer doesn't really
|
|
matter. In principle, complete object initializers could just as well
|
|
be freestanding functions to which a metatype is passed. It can make
|
|
sense to inherit a complete object initializer.
|
|
|
|
A complete object initializer must either (1) delegate (in any way) to
|
|
another complete object initializer or (2) delegate (dispatched) to a
|
|
subobject initializer of the most-derived class. It may not
|
|
initialize any ivars, although it can re-assign them after
|
|
initialization completes.
|
|
|
|
A complete object initializer soundly acts like a complete object
|
|
initializer of a class C if and only if it delegates to an initializer
|
|
which soundly acts like a complete object initializer of C.
|
|
|
|
These rules are not obvious to check statically because they're
|
|
dependent on the dynamic value of the most-derived class C. Therefore
|
|
any ability to check them depends on restricting C somehow relative to
|
|
the defining class of the initializer. Since, statically, we only
|
|
know the defining class of the initializer, we can't establish
|
|
soundness solely at the definition site; instead we have to prevent
|
|
unsound initializers from being called.
|
|
|
|
Guaranteed initializers
|
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
The chief soundness question comes back to dispatched initialization:
|
|
when can we reasonably assume that the most-derived class provides an
|
|
initializer with a given signature?
|
|
|
|
Only a complete object initializer can perform dispatched delegation.
|
|
A dispatched delegation which invokes another complete object
|
|
initializer poses no direct soundness issues. The dynamic requirement
|
|
for soundness is that, eventually, the chain of dispatches leads to a
|
|
subobject initializer that soundly acts like a complete object
|
|
initializer of the most-derived class.
|
|
|
|
Virtual initializers
|
|
~~~~~~~~~~~~~~~~~~~~
|
|
The above condition is not sufficient to make indirect initialization
|
|
sound, because it relies on the ability to simply not use an
|
|
initializer in cases where its delegation behavior isn't known to be
|
|
sound, and we can't do that to arbitrary code. For that, we would
|
|
need true virtual initializers.
|
|
|
|
A virtual initializer is a contract much more like that of a standard
|
|
virtual method: it guarantees that every subclass will either define
|
|
or inherit a constructor with a given signature, which is easy to
|
|
check at the time of definition of the subclass.
|
|
|
|
Proposal
|
|
--------
|
|
Currently, all Swift initializers are subobject initializers, and
|
|
there is no way to express the notion of a complete subobject
|
|
initializer. We propose to introduce complete subobject initializers
|
|
into Swift and to make them inheritable when we can guarantee that
|
|
doing so is safe.
|
|
|
|
Complete object initializers
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
Introduce the notion of a complete object initializer, which is
|
|
written as an initializer with ``Self`` as its return type [#]_, e.g.::
|
|
|
|
init() -> Self {
|
|
// ...
|
|
}
|
|
|
|
The use of ``Self`` here fits well with dynamic ``Self``, because a
|
|
complete object initializer returns an instance of the dynamic type
|
|
being initialized (rather than the type that defines the initializer).
|
|
|
|
A complete object initializer must delegate to another initializer via
|
|
``self.init``, which may itself be either a subobject initializer or a
|
|
complete object initializer. The delegation itself is dispatched. For
|
|
example::
|
|
|
|
class A {
|
|
var title: String
|
|
|
|
init() -> Self { // complete object initializer
|
|
self.init(withTitle:"The Next Great American Novel")
|
|
}
|
|
|
|
init withTitle(title: String) { // subobject initializer
|
|
self.title = title
|
|
}
|
|
}
|
|
|
|
Subobject initializers become more restricted. They must initialize
|
|
A's instance variables and then perform super delegation to a
|
|
subobject initializer of the superclass (if any).
|
|
|
|
Initializer inheritance
|
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
A class inherits the complete object initializers of its direct
|
|
superclass when it overrides all of the subobject initializers of its
|
|
direct superclass. Subobject initializers are never inherited. Some
|
|
examples::
|
|
|
|
class B1 : A {
|
|
var counter: Int
|
|
|
|
init withTitle(title: String) { // subobject initializer
|
|
counter = 0
|
|
super.init(withTitle:title)
|
|
}
|
|
|
|
// inherits A's init()
|
|
}
|
|
|
|
class B2 : A {
|
|
var counter: Int
|
|
|
|
init withTitle(title: String) -> Self { // complete object initializer
|
|
self.init(withTitle: title, initialCount: 0)
|
|
}
|
|
|
|
init withTitle(title: String) initialCount(Int) { // subobject initializer
|
|
counter = initialCount
|
|
super.init(withTitle:title)
|
|
}
|
|
|
|
// inherits A's init()
|
|
}
|
|
|
|
class B3 : A {
|
|
var counter: Int
|
|
|
|
init withInitialCount(initialCount: Int) { // subobject initializer
|
|
counter = initialCount
|
|
super.init(withTitle: "Unnamed")
|
|
}
|
|
|
|
init withStringCount(str: String) -> Self { // complete object initializer
|
|
var initialCount = 0
|
|
if let count = str.toInt() { initialCount = count }
|
|
self.init(withInitialCount: initialCount)
|
|
}
|
|
|
|
// does not inherit A's init(), because init withTitle(String) is not
|
|
// overridden.
|
|
}
|
|
|
|
``B3`` does not override ``A``'s subobject initializer, so it does not
|
|
inherit ``init()``. Classes ``B1`` and ``B2``, however, both inherit
|
|
the initializer ``init()`` from ``A``, because both override its only
|
|
subobject initializer, ``init withTitle(String)``. This means that one
|
|
can construct either a ``B1`` or a ``B2`` with no arguments::
|
|
|
|
B1() // okay
|
|
B2() // okay
|
|
B3() // error
|
|
|
|
That ``B1`` uses a subobject initializer to override it's superclass's
|
|
subobject initializer while ``B2`` uses a complete object initializer
|
|
has an effect on future subclasses. A few more examples::
|
|
|
|
class C1 : B1 {
|
|
init withTitle(title: String) { // subobject initializer
|
|
super.init(withTitle:title)
|
|
}
|
|
|
|
init withTitle(title: String) initialCount(Int) { // subobject initializer
|
|
counter = initialCount
|
|
super.init(withTitle:title)
|
|
}
|
|
}
|
|
|
|
class C2 : B2 {
|
|
init withTitle(title: String) initialCount(Int) { // subobject initializer
|
|
super.init(withTitle: title, initialCount:initialCount)
|
|
}
|
|
|
|
// inherits A's init(), B2's init withTitle(String)
|
|
}
|
|
|
|
class C3 : B3 {
|
|
init withInitialCount(initialCount: Int) { // subobject initializer
|
|
super.init(withInitialCount: initialCount)
|
|
}
|
|
|
|
// inherits B3's init withStringCount(str: String)
|
|
// does not inherit A's init()
|
|
}
|
|
|
|
Virtual initializers
|
|
~~~~~~~~~~~~~~~~~~~~
|
|
With the initializer inheritance rules described above, there is no
|
|
guarantee that one can dynamically dispatch to an initializer via a
|
|
metatype of the class. For example::
|
|
|
|
class D {
|
|
init() { }
|
|
}
|
|
|
|
func f(_ meta: D.Type) {
|
|
meta() // error: no guarantee that an arbitrary of subclass D has an init()
|
|
}
|
|
|
|
Virtual initializers, which are initializers that have the ``virtual``
|
|
attribute, are guaranteed to be available in every subclass of
|
|
``D``. For example, if ``D`` was written as::
|
|
|
|
class D {
|
|
@virtual init() { }
|
|
}
|
|
|
|
func f(_ meta: D.Type) {
|
|
meta() // okay: every subclass of D guaranteed to have an init()
|
|
}
|
|
|
|
Note that ``@virtual`` places a requirement on all subclasses to
|
|
ensure that an initializer with the same signature is available in
|
|
every subclass. For example::
|
|
|
|
class E1 : D {
|
|
var title: String
|
|
|
|
// error: E1 must provide init()
|
|
}
|
|
|
|
class E2 : D {
|
|
var title: String
|
|
|
|
@virtual init() {
|
|
title = "Unnamed"
|
|
super.init()
|
|
}
|
|
|
|
// okay, init() is available here
|
|
}
|
|
|
|
class E3 : D {
|
|
var title: String
|
|
|
|
@virtual init() -> Self {
|
|
self.init(withTitle: "Unnamed")
|
|
}
|
|
|
|
init withTitle(title: String) {
|
|
self.title = title
|
|
super.init()
|
|
}
|
|
}
|
|
|
|
Whether an initializer is virtual is orthogonal to whether it is a
|
|
complete object or subobject initializer. However, an inherited
|
|
complete object initializer can be used to satisfy the requirement for
|
|
a virtual requirement. For example, ``E3``'s subclasses need not
|
|
provide an ``init()`` if they override ``init withTitle(String)``::
|
|
|
|
class F3A : E3 {
|
|
init withTitle(title: String) {
|
|
super.init(withTitle: title)
|
|
}
|
|
|
|
// okay: inherited ``init()`` from E3 satisfies requirement for virtual init()
|
|
}
|
|
|
|
class F3B : E3 {
|
|
// error: requirement for virtual init() not satisfied, because it is neither defined nor inherited
|
|
}
|
|
|
|
class F3C : E3 {
|
|
@virtual init() {
|
|
super.init(withTitle: "TSPL")
|
|
}
|
|
|
|
// okay: satisfies requirement for virtual init().
|
|
}
|
|
|
|
Objective-C interoperability
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
When an Objective-C class that contains at least one
|
|
designated-initializer annotation (i.e., via
|
|
``NS_DESIGNATED_INITIALIZER``) is imported into Swift, it's designated
|
|
initializers are considered subobject initializers. Any non-designed
|
|
initializers (i.e., secondary or convenience initializers) are
|
|
considered to be complete object initializers. No other special-case
|
|
behavior is warranted here.
|
|
|
|
When an Objective-C class with no designated-initializer annotations
|
|
is imported into Swift, all initializers in the same module as the
|
|
class definition are subobject initializers, while initializers in a
|
|
different module are complete object initializers. This effectively
|
|
means that subclassing Objective-C classes without designated-initializer
|
|
annotations will provide little or no initializer inheritance, because
|
|
one would have to override nearly *all* of its initializers before
|
|
getting the others inherited. This seems acceptable so long as we get
|
|
designated-initializer annotations into enough of the SDK.
|
|
|
|
In Objective-C, initializers are always inherited, so an error of
|
|
omission on the Swift side (failing to override a subobject
|
|
initializer from a superclass) can result in runtime errors if an
|
|
Objective-C framework messages that initializer. For example, consider
|
|
a trivial ``NSDocument``::
|
|
|
|
class MyDocument : NSDocument {
|
|
var title: String
|
|
}
|
|
|
|
In Swift, there would be no way to create an object of type
|
|
``MyDocument``. However, the frameworks will allocate an instance of
|
|
``MyDocument`` and then send a message such as
|
|
``initWithContentsOfURL:ofType:error:`` to the object. This will find
|
|
``-[NSDocument initWithContentsOfURL:ofType:error:]``, which delegates
|
|
to ``-[NSDocument init]``, leaving ``MyDocument``'s stored properties
|
|
uninitialized.
|
|
|
|
We can improve the experience slightly by producing a diagnostic when
|
|
there are no initializers for a given class. However, a more
|
|
comprehensive approach is to emit Objective-C entry points for each of
|
|
the subobject initializers of the direct superclass that have not been
|
|
implemented. These entry points would immediately abort with some
|
|
diagnostic indicating that the initializer needs to be
|
|
implemented.
|
|
|
|
.. [#] Syntax suggestion from Joe Groff.
|
|
|