Properties with default values are always initialized before the body of any init(), even for Decodable.init — why is this?

Imagining a struct that:

  • contains a property with a default value and
  • a custom initializer

such as:

struct MyStruct {

    var someValue: Int = 2

    init() { }

}

I can trust that after I call MyStruct(), someValue will, of course, be 2. This makes sense in most cases, but there are cases when I feel it makes less sense, such as:

struct MyStruct {

    var someValue: Int = 2

    init(someValue: Int) {
        // implicitly: self.someValue = 2
        self.someValue = someValue
    }

}

Here, even though my intent is for this initializer to always manually initialize someValue myself, a value for it will be initialized and assigned twice: once with the default value, and again by my assignment.

Once again, this is fine in most cases — if I've given someValue a default value, usually I'll want it available to use in my init, and so it makes sense that Swift would always initialize and assign the default value.

But there are situations where this behavior has penalties, such as the default synthesized Decodable.init(from:) implementation. In these synthesized initializers, the intention is always for all properties to be derived from whatever's being decoded, and default values ignored. However this behavior is still applied, which means that all my default values will be initialized and assigned, then overwritten by the synthesized Decodable implementation's assignments. If you need to decode a type and care about performance, you essentially are forced to ditch defining default values inline, or to move them into a custom initializer as in init(someValue: Int = 2) (which, to be honest, depending on the number of properties, can be a pain to do and maintain.)

(Worth noting that this also applies to optional var properties, since they are equivalent to var myOptional = Optional<T>.none)

You can imagine other scenarios where you pay this penalty unexpectedly, like if you have default initializers that initialize values that are heavy for whatever reason (whether allocation or performance in some other regard, like generating a random number, or calling an expensive function.)

I understand that this behavior makes sense for consistency's sake, but I'm essentially curious if there's ever been thought given to alternatives, or a way to opt out — does it even make sense to give an option to opt out?

Thanks!

2 Likes

I think this is definitely worth addressing — it's caused a lot of performance problems and general confusion over the years.

The Core Team considers this behavior a bug.

6 Likes