File: InitializerInheritance.rst

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (396 lines) | stat: -rw-r--r-- 13,877 bytes parent folder | download
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.