Why does Swift initialise subclass proper fields first?

In language Swift, to initialise an instance, one has to fill in all of the fields of that class, and only then call superconstructor:

class Base {
    var name: String

    init(name: String) {
        self.name = name
    }
}

class Derived: Base {
    var number: Int

    init(name: String, number: Int) {
        // won't compile if interchange lines
        self.number = number
        super.init(name)
    }
}

For me it seems backwards, because the instance needs self to be created prior to assigning values to its fields, and that code gives impression as if the chaining happens only after the assignment. Apart from that, superclass has no legal means of reading its subclass’ introduced attributes, so safety does not count in this case.

Also, many other languages, like JavaScript, and even Objective C, which is somewhat spiritual ancestor to Swift, require chaining call before accessing self, not after.

What is the reasoning behind this choice to require the fields to be defined before calling the superconstructor?

3

In C++, when you create a Derived object, it starts out as a Base object while the Base constructor is running, so at the time the Base constructor runs, the Derived members don’t even exist. So they don’t need to be initialised, and they couldn’t possibly be initialised. Only when the Base constructor has finished is the object changed to a Derived object with lots of uninitialised fields that you then initialise.

In Swift, when you create a Derived object, it is a Derived object from the start. If methods are overridden, then the Base init method will already use the overridden methods, which might access Derived member variables. Therefore all Derived member variables must be initialised before the Base init method is called.

PS. You mentioned Objective-C. In Objective-C, everything is automatically initialised to 0 / nil / NO. But if that value is not the correct value to initialise a variable, then the Base init method could easily call a method that is overridden and uses the not yet initialised variable with a value of 0 instead of the correct value. In Objective-C, that’s not a violation of the language rules (that’s how it is defined to work) but obviously a bug in your code. In Swift, that bug is not allowed by the language.

PS. There is a comment “is it a derived object from the start, or is it not observable due to language rules”? The Derived class has initialised its own members before the Base init method is called, and these Derived members keep their values. So either it is a Derived object at the time Base init is called, or the compiler would have to do something rather bizarre. And right after the Base init method has initialized all Base instance members, it can call overridden functions and that will prove it is an instance of the derived class.

2

This comes from Swift’s safety rules, as explained in the section Two-Phase Initialization on the language doc’s Initialization page.

It ensures that every field is set before use (esp. pointers, to avoid crashes).

Swift achieves this with a two-phase initialization sequence: Each initializer must initialize all of its instance fields, then call a superclass initializer to do likewise, and only after that happens up the tree are these initializers allowed to let the self pointer escape, call instance methods, or read the values of instance properties.

They can then do further initialization, assured that the object is well-formed. In particular, all non-optional pointers will have valid values. nil is not valid for them.

Objective C isn’t much different except that 0 or nil is always a valid value, so the first phase initialization is done by the allocator just setting all fields to 0. Also, Swift has immutable fields, so they must get initialized in phase one. And Swift enforces these safety rules.

1

Consider

  • A virtual method defined in the base class can be redefined in the derived class.
  • The contractor of the base class may call this virtual method directly or indirectly.
  • The redefined virtual method (in the derived class) may depend on the value of a field in the derived class being set correctly in the derived class contractor.
  • The derived class’s contractor may call a method in the base class that depends on fields having been set in the base class contractor.

Therefore there is no simple design that make contractor safe when virtual methods are allowed, Swift avoids these issues by requiring Two-Phase Initialization, hence giving better safety to the programmer, while also resulting in a more complex language.

If you can solve these issues in a nice way, do not pass “Go”, proceed directly to the collection of your PHd…

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *