[Proposal Draft] partial initializers

Let's start with this:

, and the compiler will need to make sure partial method calls are ordered such that everything needed is already initialized; it's simply a question of whether we declare what's needed or automatically detect it. I've noticed in previous proposals that scanning the method body is disfavored, so I figure that's not our best bet here.

This is another reason I am not sure about this. I think you are right that this would require a declaration. It becomes much more verbose that way.

The compiler will absolutely, 100%, inescapably need to know which properties a given partial method initializes. Otherwise it can't properly perform its phase one check, and if this proposal breaks safe initialization, it's not going to be accepted.

We could specify that the compiler takes a peek at the implementation and figures out which fields it initializes, or we could specify that the method declares the fields it will initialize. But however we choose to do it, doing it in *some* way is not negotiable.

Yes of course it needs to know about what properties are initialized. It does not need to know about which properties have been initialized prior to calling the partial initializer though if we don’t allow it to read pre-initialized properties.

I think that many, possibly most, partial methods—especially those that are meant to be called as normal methods will need to access existing properties

Do you have some concrete examples? I’m sure this would be useful in some cases, I’m just not sure whether we need to include it right away.

Sure. Let's extend your resetting example so that the value you can reset the property *to* is configurable:

  struct Resettable<Value> {
    private let initialValue: Value
    var value: Value
    
    partial(uses initialValue, inits value) mutating func reset() {
      value = initialValue
    }
    
    init(_ initialValue: Value) {
      self.initialValue = initialValue
      reset()
    }
  }

This is a reasonable example. John, if you’re still following this thread does allowing reads from properties that must be initialized prior to calling a partial init seem worthwhile to include? Or does it seem like something that would make the proposal more likely to be rejected?

Also, given the comments regarding the memberwise init proposal, is a partial initializer proposal reasonable to continue pursuing right now? Or is this something that should wait?

If the memberwise init review is anything to go by, there will be a lot of pushback that something like this makes the proposal too complex. People might warm to it more after using the basic feature for a while and realizing the limitation this poses in practice.

You don't want to make things overly complicated, but you also don't want to ignore obvious needs. In hindsight, SE-0019 made both of those mistakes in different places.

I think you mean SE-0018. I learned a couple of things during that process.

One is that it is really hard to tell how to strike an appropriate balance if you want to do something nontrivial. It seems like there is a lot of pressure to keep proposals very minimal (I tried very hard to get a solution to `let` defaults into the proposal for example). This means necessarily choosing which needs to address because addressing all “obvious" needs makes the proposal too large and likely too complex. Additionally, which needs are “obvious” is in the eye of the beholder.

Another thing is that it’s easy to start with the wrong premise. In this case, the idea was to enhance the existing memberwise init feature, keeping changes as small as possible to increase chances of the proposal being accepted. That just wasn’t the right place to start to address boilerplate in explicit / nontrivial initializers. I realized the “automatic” model was likely too complex and the “opt-in” model was probably better just before the review began but it was too late to change the proposal. And I didn’t give enough consideration to trying to find a more general way to solve the problem (such as partial initializers) until well into the review discussion.

One thing I would be concerned about is that the compiler will enforce proper order during initialization, but it will not enforce anything later. If you are resetting state you will be on your own to do things in the correct order. If the partial inits are not able to read from a property they didn’t write that at least helps prevent mistakes during the reset sequence (of course at the cost of some flexibility in structuring your code).

I'm not really sure why this is a concern. After `init` finishes, none of the fields you access could be uninitialized. There's nothing built in to Swift that lets you ensure methods are only called when the instance is in a state that expects them; all you've got for that is precondition(). I just don't see how this is any worse than what we've already got.

Well it would have to ensure that sequencing during initialization for a partial initializer that reads from a property it does not set. My point wasn’t that something would be uninitialized later, but rather that your code might rely on “re-initialization” to happen in a certain sequence to be correct. It might lead to mistakes when the compiler enforces sequencing during initialization but not later.

Of course you are right that you can “re-initialize” vars already today without any ordering guarantee so it wouldn’t make new mistakes possible. On balance it probably would make things better by allowing you to factor out code.

I’m not opposed to allowing this. I just wouldn't want to include it only to find out later that it is a sticking point that leads to rejection of the proposal.

Matthew

···

On Jan 13, 2016, at 3:15 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

I know there's a new thread now, but this is simply incorrect. Consider a struct with a single, non-optional AnyObject field:

struct Ref {
  var referent: AnyObject
  init func resetTo(newReferent: AnyObject) {
    referent = newReferent
  }
  init(referent: AnyObject) {
    resetTo.init(referent)
  }
}

If 'referent' has already been set, the assignment has to release the old object; if it hasn't, it must not touch that memory (which is presumably uninitialized).

I'm not sure if this practically affects the proposal in any way, other than making it a little harder to implement. But it's an important part of the model.

Best,
Jordan

more comments coming on the other thread

···

On Jan 13, 2016, at 8:27, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

The compiler will absolutely, 100%, inescapably need to know which properties a given partial method initializes. Otherwise it can't properly perform its phase one check, and if this proposal breaks safe initialization, it's not going to be accepted.

We could specify that the compiler takes a peek at the implementation and figures out which fields it initializes, or we could specify that the method declares the fields it will initialize. But however we choose to do it, doing it in *some* way is not negotiable.

Yes of course it needs to know about what properties are initialized. It does not need to know about which properties have been initialized prior to calling the partial initializer though if we don’t allow it to read pre-initialized properties.

The compiler will absolutely, 100%, inescapably need to know which properties a given partial method initializes. Otherwise it can't properly perform its phase one check, and if this proposal breaks safe initialization, it's not going to be accepted.

We could specify that the compiler takes a peek at the implementation and figures out which fields it initializes, or we could specify that the method declares the fields it will initialize. But however we choose to do it, doing it in *some* way is not negotiable.

Yes of course it needs to know about what properties are initialized. It does not need to know about which properties have been initialized prior to calling the partial initializer though if we don’t allow it to read pre-initialized properties.

I know there's a new thread now, but this is simply incorrect. Consider a struct with a single, non-optional AnyObject field:

struct Ref {
  var referent: AnyObject
  init func resetTo(newReferent: AnyObject) {
    referent = newReferent
  }
  init(referent: AnyObject) {
    resetTo.init(referent)
  }
}

If 'referent' has already been set, the assignment has to release the old object; if it hasn't, it must not touch that memory (which is presumably uninitialized).

I'm not sure if this practically affects the proposal in any way, other than making it a little harder to implement. But it's an important part of the model.

Thanks for catching this Jordan. I was discussing requirements imposed by the partial initializer itself but of course any cleanup necessary must also happen and I didn’t fully consider that.

John asked that the proposal disallow redundant assignment to make implementation easier, which is included in the new draft. I asked him whether this should apply to initial values for `var` properties but never got an answer. I think the point you make indicates why he made the request which also indicates disallowing writing to a `var` with an initial value, which I currently allowed for in the proposal.

I am open to modifying the proposal in whatever way the compiler team thinks is best. :) Feel free to respond with your thoughts in either thread.

Matthew

···

On Jan 20, 2016, at 5:29 PM, Jordan Rose <jordan_rose@apple.com> wrote:

On Jan 13, 2016, at 8:27, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Best,
Jordan

more comments coming on the other thread