State of the Memberwise Initializer

State of the Memberwise Initializer

Prolog

Hello Evolution,

I want to discuss where we are with the current memberwise initializer for structures and where I wish to see this initializer evolve.

Refer to SE-0018 and SE-0018 Rationale. I do not speak for the author here, but I'm rathering pulling ideas and references from SE-0018 to formulate this post.

From here on out I'm going to refer to the memberwise initializer as MWI

Note that this is not technically a complete resurrection of SE-0018 because I will be stating my opinion on where we should go with the MWI.

Also, forgive me for restraining myself from saying "State of the Memberwise Initializer Address"

What is this memberwise initializer?

For those that don't know what exactly the MWI is, at the most basic level, the MWI is just a special compiler synthesized initializer for structures.

Given:

struct Dog {
  var age: Int
  var name: String
}

The compiler synthesizes a MWI for this structure which looks like this:

init(age: Int, name: String)

This initializer allows users to be able to initialize and instance of type Dog like Dog(age: 2, name: "sparky"). The MWI is a pretty fantastic initializer which frees the user of having to write boiler plate initializers which effectively do the same thing.

What's the problem with the memberwise initializer?

While the MWI is a fantastic gift from the compiler, it has a number of limitations which hold it back from being an even greater initializer. In SE-0018, it quotes Chris Lattner who spelled out some limitations that the MWI currently has:

The default memberwise initializer behavior of Swift has at least these deficiencies (IMO):

  1. Defining a custom init in a struct disables the memberwise initializer, and there is no easy way to get it back.
  2. Access control + the memberwise init often requires you to implement it yourself.
  3. We don’t get memberwise inits for classes.
  4. var properties with default initializers should have their parameter to the synthesized initializer defaulted.
  5. lazy properties with memberwise initializers have problems (the memberwise init eagerly touches it).

To this day, each of those 5 limitations are still in existance for the MWI. Below I have made examples excercising these limitations.

Limitation 1: Custom init disables the MWI

struct Dog {
  var age: Int
  var name: String
	
  init(ageInYears: Int, legalName: String) {
    ...
  }
}

let sparky = Dog(age: 2, name: "sparky") // Incorrect argument labels in call (have 'age:name:', expected 'ageInYears:legalName:')

Limitation 2: Access Control

public struct Dog {
  public var age: Int
  public var name: String
}

let sparky = Dog(age: 2, name: "sparky") // 'Dog' initializer is inaccessible due to 'internal' protection level

Limitation 3: No class support

class Dog { // Class 'Dog' has no initializers
  var age: Int // stored property 'age' without initial value prevents synthesized initializers
  var name: String // stored property 'name' without initial value prevents synthesized initializers
}

let sparky = Dog(age: 2, name: "sparky") // 'Dog' cannot be constructed because it has no accessible initializers

Limitation 4: No default values for vars with default initializers

struct Dog {
  var age: Int = 0
  var name: String
}

// Let's use the default age and just assign name
let sparky = Dog(name: "sparky") // Missing argument for parameter 'age' in call

Limitation 5: Lazy properties

In order to fully understand this one, it's best to understand how lazy properties work under the hood.

Given:

struct DogInfo {
  var age: Int
  var name: String
	
  init(age: Int, name: String) {
    print("Making dog info...")
	
    self.age = age
    self.name = name
  }
}

struct Dog {
  lazy var info: DogInfo = DogInfo(age: 0, name: "")
}

The compiler synthesizes something like this:

struct Dog {
  lazy var info: DogInfo {
    mutating get {
      if let storage = info.storage { return storage }

      let initialValue = DogInfo(age: 0, name: "")
      info.storage = initialValue
      return initialValue
    }

    set {
      info.storage = newValue
    }
  }

  private var info.storage: DogInfo?
}

Now that we understand how lazy properties work, where does the MWI come into this? The synthesized MWI for the example above looks something like this:

init(info: DogInfo?)

Seems easy enough, lets try using it in action.

let sparky = Dog(info: DogInfo(age: 2, name: "sparky"))
// Making dog info...

What? I haven't even accessed the info property yet? What's going on? Well, what's really happening is that the value passed for info is actually setting the info.storage property with immediate execution of the expression. Effectively taking the "lazy" out of the lazy. (If I got anything wrong here, please feel free to correct me.)

What can we do to solve these limitations?

SE-0018 was a good first step towards abolishing these limitations, but unfortunately the proposal was premature for its time. It was deferred for good reasons that the Rationale lays out. I think it's best to split SE-0018 into 2 parts for what it wants to accomplish: "Powerful MWI" and "Flexible MWI". I'm only going to discuss the first half, "Powerful MWI".

Powerful Memberwise Initializer

What in the world is a "Powerful MWI"? My idea of what a powerful MWI is is a future MWI in which it solves 4/5 current limitations. I'm excluding class support for now. Read Tweet #1 and Tweet #2 for why class support has a number of lingering questions to be asked/answered. Solving some of these limitations like default values for variables that have default initialers, access control, and disabling the MWI with presence of a custom init are all problems my "Powerful MWI" aim to solve. Other features like opting in/out for this MWI and opting properties in/out are out of scope for my "Powerful MWI" and should be reserved for "Flexible MWI".

Epilog

This isn't exactly a proposal, but I simply wanted to get this topic discussed again because as we inch closer and closer to Swift 5, I think it might be worth to get some of these limitations solved. I'm curious what everyone thinks about my "Powerful MWI" idea.

Implementing default values for variables with default initializers

I've actually already implemented a solution for Limitation 4 (No default values for variables with default initializers) here: apple/swift#19743. I would love to propose this to start solving these limitations if evolution deems that this is the way to go.

19 Likes

I usually consider Limitation 1 to be a feature. I like having a succinct way to restrict the initialization of some properties.* Broadly agreed with the pitch, but I'd hope that the ability to easily express "I don't want the full memberwise initializer" be preserved (somehow -- doesn't have to be the current way).

*Note that the way to keep the MWI is just to declare the other initializer in an extension, which I've never found onerous.

2 Likes

Limitation #2: It would be nice to have a way to signal to the compiler that you want the default generated publicly, but I don't think it should do it by default. Public API should be explicitly written.

I would love to see #4 behave as one would expect.

1 Like

As the author of SE-0018 I obviously have a lot of interest in this topic! I've been very busy and haven't had a chance to reply until now.

As I mentioned in your pitch thread, I think you chose the right place to start for a small enhancement that might have a chance to make it in the Swift 5 timeframe. I also agree with the choice you mention in this post to omit support for classes. SE-0018 was the first proposal I worked on. Trying to include support for classes in that proposal was a mistake.

I believe addressing the first two limitations you discuss requires committing to a direction for the final solution as both will require introducing new syntax. SE-0018 received an enormous amount of feedback and I would expect the same next time around. For that reason, I think we should wait to wait to have the discussion until this feature falls within the scope of a Swift release and there are implementers willing to participate (a proposal won't be reviewed without a draft implementation).