1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
|
: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.
|