[Proposal] Partial initializers


(Matthew Johnson) #1

I have completed the second draft of my partial initializers proposal (the first really complete draft).

This proposal also includes some discussion of memberwise initialization at John McCall’s request. If anyone would like to continue discussing that topic informally I will be happy to do so, however any such discussion should happen on one of the existing memberwise initialization threads or on a new thread related to that topic. Please do not let that section of the document be a distraction from the partial initializer proposal itself.

The new draft can be found here:

https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md

I really appreciate any feedback you have!

-Matthew


(David Owens II) #2

I like the updates. I think the distinction between partial inits and init functions is extremely subtle. The only difference is that partial init can set `let` members, correct?

The only feedback I really have is that this makes the initialization rules even more complicated. I don't know how to address that though. =)

-David

···

On Jan 14, 2016, at 8:08 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

I have completed the second draft of my partial initializers proposal (the first really complete draft).

This proposal also includes some discussion of memberwise initialization at John McCall’s request. If anyone would like to continue discussing that topic informally I will be happy to do so, however any such discussion should happen on one of the existing memberwise initialization threads or on a new thread related to that topic. Please do not let that section of the document be a distraction from the partial initializer proposal itself.

The new draft can be found here:

https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md

I really appreciate any feedback you have!

-Matthew

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jordan Rose) #3

Hm. Some comments:

This proposal does not introduce extensions with stored properties, however partial initialization is closely related to that topic. Class extensions with stored properties would be required to have an extension initializer. That extension initializer would effectively be treated as a partial initializer by designated initializers of the class.

I feel like most "extensions with stored properties" proposals have to cover extensions from another module, but maybe those properties are required to be lazy or something.

3. Partial initializers and initialization methods may include an access control modifier specifying their visibility.

I'm a little concerned about exposing these things publicly: there's no point if the struct or class has non-public fields (because new non-delegating initializers can't initialize the non-public fields), and by default every struct and class is allowed to add non-public fields in new versions of a library. (See the LibraryEvolution.rst doc for more info.) I can kind of see making these private vs. internal, but that has other issues (see below).

Even in the case where all fields are public and the struct promises not to change any fields, you'd also have to promise that the partial initializer never changes which properties it's initializing; that's now part of the binary interface of the file. That's unusual for something that behaves like a function; usually the body is not part of the ABI.

I'd much rather just ban 'public' and never have partial inits be part of a library's public interface. Public initializer methods would be presented as plain methods, with no hint of their initializer origins.

9. The compiler keeps track of the properties initialized by a partial initializer or initialization method and uses that knowledge when enforcing initialization rules in phase one in the calling initializer.

If a struct partial initializer is defined in another file, this would require reading the body of the initializer to determine what variables it initializes. We currently don't type-check any bodies outside of the primary source file when doing a normal, multi-process compilation.

You're missing a DI condition here in the sub-bullets, which is that the partial initializer must initialize its subset of stored properties on all paths.

9 iv. A partial initializer or initialization method can only write to a property once, including var properties.

I'm not sure why John thinks this would make things easier but I'll believe him. Seems like it'd be the same as doing it in phase 1 of an initializer.

···

---

Can an initializer method be overridden? If so, I assume that does not override the "initializer" part, just the "method" part? If not, can we require the 'final' to be explicit?

Can the method be marked @objc? 'dynamic'? I would assume "yes", because it's not really different from any other method, but I think it ought to be called out.

I don't personally like the call syntax but I don't immediately have anything better to offer. I do think that part of this proposal should include naming guidelines for these things.

What happens when you call an init method from phase 2 of initialization? Is that just illegal?

I'm ultimately still not convinced that this is the right way to reduce initializer boilerplate (phase 1 duplication between initializers, or shared code between initializers and methods). It again adds complexity to the language, and that added complexity may not be worth the payoff. I'm thinking about someone coming across the construct for the first time: would they understand what it was doing? Would they understand the error messages they would get?

(Specifically on that note: "init func" is probably okay because it's a special kind of func, but I think I would personally prefer to keep the 'partial' keyword for partial inits, even though it's technically unnecessary.)

Sorry for the somewhat scattered thoughts. Hope it's helpful.
Jordan

On Jan 14, 2016, at 20:08, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

I have completed the second draft of my partial initializers proposal (the first really complete draft).

This proposal also includes some discussion of memberwise initialization at John McCall’s request. If anyone would like to continue discussing that topic informally I will be happy to do so, however any such discussion should happen on one of the existing memberwise initialization threads or on a new thread related to that topic. Please do not let that section of the document be a distraction from the partial initializer proposal itself.

The new draft can be found here:

https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md

I really appreciate any feedback you have!

-Matthew

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #4

I like the updates. I think the distinction between partial inits and init functions is extremely subtle. The only difference is that partial init can set `let` members, correct?

Hi David, glad you like the changes.

Yes, that is the only difference, aside from the declaration syntax. It would be better if we didn’t need two separate forms but either one would sacrifice desirable functionality.

Giving up the ability to initialize a `let` is unacceptable IMO. Giving up the ability to share code that initializes `var` properties with post-initialization callers is also undesirable.

I considered having only once concept and allowing it to be called post-initialization if it only initializes `var` properties but I don’t like that approach. The compiler wouldn’t be able to check intent at the site of declaration (i.e. an init func that attempts to assign to a `let`) and it wouldn’t be clear to users which partial initializers could be called post-initialization and which cannot.

That leaves us with the need for two very similar concepts. The only reasonable alternative seems to be dropping `init func`. I would do that it if it became a hurdle to accepting the proposal, but I think it will be a common use case and is worth including.

Do you have any further thoughts? Do you agree with the decision I made here?

The only feedback I really have is that this makes the initialization rules even more complicated. I don't know how to address that though. =)

Agree, the rules are essential complexity if we want safe initialization.

I think the small incremental increase in complexity is a worthwhile price to pay for the ability to factor initialization code. This is especially true if we’re going to add stored properties in extensions. I wouldn’t want to see a situation where we could have partial initializers, but only when we move some properties into an extension.

I hope you agree.

-Matthew

···

On Jan 15, 2016, at 10:12 PM, David Owens II <david@owensd.io> wrote:

-David

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

I have completed the second draft of my partial initializers proposal (the first really complete draft).

This proposal also includes some discussion of memberwise initialization at John McCall’s request. If anyone would like to continue discussing that topic informally I will be happy to do so, however any such discussion should happen on one of the existing memberwise initialization threads or on a new thread related to that topic. Please do not let that section of the document be a distraction from the partial initializer proposal itself.

The new draft can be found here:

https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md

I really appreciate any feedback you have!

-Matthew

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Brent Royal-Gordon) #5

The only difference is that partial init can set `let` members, correct?

The other difference (unless I'm wrong) is that an init function can be called again after initialization is complete. In other words, an `init func` is an actual method that can also be called during initialization; an `init <name>` is a sort of pseudo-method that can *only* be called during initialization.

···

--
Brent Royal-Gordon
Architechies


(Slava Pestov) #6

3. Partial initializers and initialization methods may include an access control modifier specifying their visibility.

I'm a little concerned about exposing these things publicly: there's no point if the struct or class has non-public fields (because new non-delegating initializers can't initialize the non-public fields), and by default every struct and class is allowed to add non-public fields in new versions of a library. (See the LibraryEvolution.rst doc for more info.) I can kind of see making these private vs. internal, but that has other issues (see below).

Even in the case where all fields are public and the struct promises not to change any fields, you'd also have to promise that the partial initializer never changes which properties it's initializing; that's now part of the binary interface of the file. That's unusual for something that behaves like a function; usually the body is not part of the ABI.

I'd much rather just ban 'public' and never have partial inits be part of a library's public interface. Public initializer methods would be presented as plain methods, with no hint of their initializer origins.

9. The compiler keeps track of the properties initialized by a partial initializer or initialization method and uses that knowledge when enforcing initialization rules in phase one in the calling initializer.

If a struct partial initializer is defined in another file, this would require reading the body of the initializer to determine what variables it initializes. We currently don't type-check any bodies outside of the primary source file when doing a normal, multi-process compilation.

You're missing a DI condition here in the sub-bullets, which is that the partial initializer must initialize its subset of stored properties on all paths.

I believe you can already factor out common code in initializers by defining @_transparent functions. Would it be more worthwhile to improve @_transparent by fixing the known limitations instead?

What happens when you call an init method from phase 2 of initialization? Is that just illegal?

Note that @_transparent functions can be called in phase 1 or phase 2 — DI figures out which sets are initialization stores, and which ones release the old value.

···

On Jan 20, 2016, at 6:48 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

I'm ultimately still not convinced that this is the right way to reduce initializer boilerplate (phase 1 duplication between initializers, or shared code between initializers and methods). It again adds complexity to the language, and that added complexity may not be worth the payoff. I'm thinking about someone coming across the construct for the first time: would they understand what it was doing? Would they understand the error messages they would get?

(Specifically on that note: "init func" is probably okay because it's a special kind of func, but I think I would personally prefer to keep the 'partial' keyword for partial inits, even though it's technically unnecessary.)

Sorry for the somewhat scattered thoughts. Hope it's helpful.
Jordan

On Jan 14, 2016, at 20:08, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I have completed the second draft of my partial initializers proposal (the first really complete draft).

This proposal also includes some discussion of memberwise initialization at John McCall’s request. If anyone would like to continue discussing that topic informally I will be happy to do so, however any such discussion should happen on one of the existing memberwise initialization threads or on a new thread related to that topic. Please do not let that section of the document be a distraction from the partial initializer proposal itself.

The new draft can be found here:

https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md

I really appreciate any feedback you have!

-Matthew

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #7

Hm. Some comments:

Thanks for providing lots of feedback!

This proposal does not introduce extensions with stored properties, however partial initialization is closely related to that topic. Class extensions with stored properties would be required to have an extension initializer. That extension initializer would effectively be treated as a partial initializer by designated initializers of the class.

I feel like most "extensions with stored properties" proposals have to cover extensions from another module, but maybe those properties are required to be lazy or something.

3. Partial initializers and initialization methods may include an access control modifier specifying their visibility.

I'm a little concerned about exposing these things publicly: there's no point if the struct or class has non-public fields (because new non-delegating initializers can't initialize the non-public fields), and by default every struct and class is allowed to add non-public fields in new versions of a library. (See the LibraryEvolution.rst doc for more info.) I can kind of see making these private vs. internal, but that has other issues (see below).

Even in the case where all fields are public and the struct promises not to change any fields, you'd also have to promise that the partial initializer never changes which properties it's initializing; that's now part of the binary interface of the file. That's unusual for something that behaves like a function; usually the body is not part of the ABI.

I'd much rather just ban 'public' and never have partial inits be part of a library's public interface. Public initializer methods would be presented as plain methods, with no hint of their initializer origins.

I have no problem with banning `public`partial inits and only exposing the ordinary method aspect of `public` initializer methods. Unless this is controversial on the core team I will make this change.

9. The compiler keeps track of the properties initialized by a partial initializer or initialization method and uses that knowledge when enforcing initialization rules in phase one in the calling initializer.

If a struct partial initializer is defined in another file, this would require reading the body of the initializer to determine what variables it initializes. We currently don't type-check any bodies outside of the primary source file when doing a normal, multi-process compilation.

Are you recommending a same-file requirement? I wouldn’t have a problem with that. I’m trying to keep this proposal as focused as possible and will make any changes necessary to address implementation considerations.

You're missing a DI condition here in the sub-bullets, which is that the partial initializer must initialize its subset of stored properties on all paths.

Agree. I left that implicit but will call it out explicitly.

9 iv. A partial initializer or initialization method can only write to a property once, including var properties.

I'm not sure why John thinks this would make things easier but I'll believe him. Seems like it'd be the same as doing it in phase 1 of an initializer.

I may have misinterpreted John’s comment. I’m guessing it had to do with releasing the old value if it was set prior to calling the partial init like you mentioned in the other thread. Hopefully he can clarify.

---

Can an initializer method be overridden? If so, I assume that does not override the "initializer" part, just the "method" part? If not, can we require the 'final' to be explicit?

Good question. I was thinking of them as implicitly `final` but didn’t call that out. If you think they should be explicitly marked `final` I will make that change.

Can the method be marked @objc? 'dynamic'? I would assume "yes", because it's not really different from any other method, but I think it ought to be called out.

I suppose so if that doesn’t create any implementation challenges around the initializer aspect.

I don't personally like the call syntax but I don't immediately have anything better to offer. I do think that part of this proposal should include naming guidelines for these things.

I don’t love it but don’t have a better idea either. It’s a good candidate for bike shedding and we might be able to find something better.

What do you have in mind in terms of naming guidelines? I can imagine UI classes having partial inits like “appearanceProperties” and “animationProperties”, etc. Those names wouldn’t be good for an initializer method though.

What happens when you call an init method from phase 2 of initialization? Is that just illegal?

Good question. I should have addressed this. Partial initializers should not be callable during phase 2.

It probably make senses to allow initializer methods to be called during phase 2 using the usual method syntax just like any other method. What do others think?

I'm ultimately still not convinced that this is the right way to reduce initializer boilerplate (phase 1 duplication between initializers, or shared code between initializers and methods). It again adds complexity to the language, and that added complexity may not be worth the payoff. I'm thinking about someone coming across the construct for the first time: would they understand what it was doing? Would they understand the error messages they would get?

What I like about this approach is that it is a general tool for factoring initialization code using techniques similar to those we use in other code. Lots of people do similar things in Objective-C, but in an obviously unsafe way. This would help ease their migration to Swift while helping them do this safely.

I think the best way to think about the added complexity is to consider the *incremental* complexity and benefit over Swift’s initialization model. I think it is a pretty modest change to understand for those who already understand the existing rules while providing substantial benefit in some cases.

(Specifically on that note: "init func" is probably okay because it's a special kind of func, but I think I would personally prefer to keep the 'partial' keyword for partial inits, even though it's technically unnecessary.)

I would be happy with whatever syntax the core team thinks is best. I like keeping it more concise but don’t mind `partial` if others like that better.

It’s just hard to tell what that is with several people involved. Any recommendations on how to handle this when writing a proposal?

Sorry for the somewhat scattered thoughts. Hope it's helpful.

Absolutely. I really appreciate your feedback!

-Matthew

···

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

Jordan

On Jan 14, 2016, at 20:08, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I have completed the second draft of my partial initializers proposal (the first really complete draft).

This proposal also includes some discussion of memberwise initialization at John McCall’s request. If anyone would like to continue discussing that topic informally I will be happy to do so, however any such discussion should happen on one of the existing memberwise initialization threads or on a new thread related to that topic. Please do not let that section of the document be a distraction from the partial initializer proposal itself.

The new draft can be found here:

https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md

I really appreciate any feedback you have!

-Matthew

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #8

The only difference is that partial init can set `let` members, correct?

The other difference (unless I'm wrong) is that an init function can be called again after initialization is complete. In other words, an `init func` is an actual method that can also be called during initialization; an `init <name>` is a sort of pseudo-method that can *only* be called during initialization.

This is correct pretty much correct. I don’t really think of `init <name>` as a pseudo-method but you could look at it that way. Regardless of how you think of it, the current proposal suggests using `name.init()` syntax when calling either form during initialization.

I know you want to be able to reference previously initialized members in a partial initializer. I still have an open mind about this, but want to see more community and core team input before I consider it further.

Aside from that, how do you like the changes in this draft in general?

Matthew

···

On Jan 19, 2016, at 11:05 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

--
Brent Royal-Gordon
Architechies


(Slava Pestov) #9

Adding more details…

I'm a little concerned about exposing these things publicly: there's no point if the struct or class has non-public fields (because new non-delegating initializers can't initialize the non-public fields), and by default every struct and class is allowed to add non-public fields in new versions of a library. (See the LibraryEvolution.rst doc for more info.) I can kind of see making these private vs. internal, but that has other issues (see below).

Even in the case where all fields are public and the struct promises not to change any fields, you'd also have to promise that the partial initializer never changes which properties it's initializing; that's now part of the binary interface of the file. That's unusual for something that behaves like a function; usually the body is not part of the ABI.

I'd much rather just ban 'public' and never have partial inits be part of a library's public interface. Public initializer methods would be presented as plain methods, with no hint of their initializer origins.

If we just go with @_transparent functions for partial initialization, there are no new rules to add regarding ABI resilience. :slight_smile:

I'm ultimately still not convinced that this is the right way to reduce initializer boilerplate (phase 1 duplication between initializers, or shared code between initializers and methods). It again adds complexity to the language, and that added complexity may not be worth the payoff.

This is really my main concern with the proposal.

Also I would suggest that the part regarding argument forwarding could probably be split off and discussed separately — I think this has applicability outside of initializers.

Slava

···

On Jan 20, 2016, at 8:46 PM, Slava Pestov via swift-evolution <swift-evolution@swift.org> wrote:

Jordan

On Jan 14, 2016, at 20:08, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I have completed the second draft of my partial initializers proposal (the first really complete draft).

This proposal also includes some discussion of memberwise initialization at John McCall’s request. If anyone would like to continue discussing that topic informally I will be happy to do so, however any such discussion should happen on one of the existing memberwise initialization threads or on a new thread related to that topic. Please do not let that section of the document be a distraction from the partial initializer proposal itself.

The new draft can be found here:

https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md

I really appreciate any feedback you have!

-Matthew

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jordan Rose) #10

IIRC mandatory inlining runs after DI. I think Chris didn't want cases where the DI looked wrong but was correct because the called function happened to be inlined—there's no indication at the call site of this.

We also wouldn't want the other semantics of transparent <https://github.com/apple/swift/blob/master/docs/TransparentAttr.rst>: no debug info, and restrictions on what can be mentioned if it's marked public.

Jordan

···

On Jan 20, 2016, at 20:46 , Slava Pestov <spestov@apple.com> wrote:

On Jan 20, 2016, at 6:48 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

3. Partial initializers and initialization methods may include an access control modifier specifying their visibility.

I'm a little concerned about exposing these things publicly: there's no point if the struct or class has non-public fields (because new non-delegating initializers can't initialize the non-public fields), and by default every struct and class is allowed to add non-public fields in new versions of a library. (See the LibraryEvolution.rst doc for more info.) I can kind of see making these private vs. internal, but that has other issues (see below).

Even in the case where all fields are public and the struct promises not to change any fields, you'd also have to promise that the partial initializer never changes which properties it's initializing; that's now part of the binary interface of the file. That's unusual for something that behaves like a function; usually the body is not part of the ABI.

I'd much rather just ban 'public' and never have partial inits be part of a library's public interface. Public initializer methods would be presented as plain methods, with no hint of their initializer origins.

9. The compiler keeps track of the properties initialized by a partial initializer or initialization method and uses that knowledge when enforcing initialization rules in phase one in the calling initializer.

If a struct partial initializer is defined in another file, this would require reading the body of the initializer to determine what variables it initializes. We currently don't type-check any bodies outside of the primary source file when doing a normal, multi-process compilation.

You're missing a DI condition here in the sub-bullets, which is that the partial initializer must initialize its subset of stored properties on all paths.

I believe you can already factor out common code in initializers by defining @_transparent functions. Would it be more worthwhile to improve @_transparent by fixing the known limitations instead?


(Jordan Rose) #11

9. The compiler keeps track of the properties initialized by a partial initializer or initialization method and uses that knowledge when enforcing initialization rules in phase one in the calling initializer.

If a struct partial initializer is defined in another file, this would require reading the body of the initializer to determine what variables it initializes. We currently don't type-check any bodies outside of the primary source file when doing a normal, multi-process compilation.

Are you recommending a same-file requirement? I wouldn’t have a problem with that. I’m trying to keep this proposal as focused as possible and will make any changes necessary to address implementation considerations.

That would be one way to solve this, but that's kind of at odds with your direction of "partial initializers for stored properties in extensions". There's also a circularity problem, where type-checking one partial init requires looking at the body of another, which mistakenly calls the first, but we can probably handle that error.

The other way that comes to mind would be to explicitly declare what's being initialized, as Brent (and others?) mentioned. That's certainly not as concise but it does fix the problem. I don't think it counts as a leak of information about stored properties as long as partial inits are never exposed publicly.

Can an initializer method be overridden? If so, I assume that does not override the "initializer" part, just the "method" part? If not, can we require the 'final' to be explicit?

Good question. I was thinking of them as implicitly `final` but didn’t call that out. If you think they should be explicitly marked `final` I will make that change.

Well, I think it's a good idea. Others may disagree. It could also be required only for public init methods, because private and internal ones don't make an API contract.

Can the method be marked @objc? 'dynamic'? I would assume "yes", because it's not really different from any other method, but I think it ought to be called out.

I suppose so if that doesn’t create any implementation challenges around the initializer aspect.

Yeah, they wouldn't apply to the initializer, only the method. I don't know if that's weird enough to forbid 'dynamic', but it seems benign for '@objc'. (AFAICT the effects of '@objc' are only observable after phase 1 completes anyway.)

I don't personally like the call syntax but I don't immediately have anything better to offer. I do think that part of this proposal should include naming guidelines for these things.

I don’t love it but don’t have a better idea either. It’s a good candidate for bike shedding and we might be able to find something better.

What do you have in mind in terms of naming guidelines? I can imagine UI classes having partial inits like “appearanceProperties” and “animationProperties”, etc. Those names wouldn’t be good for an initializer method though.

I think that does give us a place to start: initializer methods are named like methods but invoked in some special way. Maybe partial inits are named as methods as well. (Maybe we just drop partial inits to make everything simpler, and only have init methods. They can get dead-code-stripped if they're only used as partial inits.)

What happens when you call an init method from phase 2 of initialization? Is that just illegal?

Good question. I should have addressed this. Partial initializers should not be callable during phase 2.

It probably make senses to allow initializer methods to be called during phase 2 using the usual method syntax just like any other method. What do others think?

I'm inclined to agree.

I'm ultimately still not convinced that this is the right way to reduce initializer boilerplate (phase 1 duplication between initializers, or shared code between initializers and methods). It again adds complexity to the language, and that added complexity may not be worth the payoff. I'm thinking about someone coming across the construct for the first time: would they understand what it was doing? Would they understand the error messages they would get?

What I like about this approach is that it is a general tool for factoring initialization code using techniques similar to those we use in other code. Lots of people do similar things in Objective-C, but in an obviously unsafe way. This would help ease their migration to Swift while helping them do this safely.

I do like the idea of "you can factor out code to helper methods, but initialization code is special, so you factor it out to special kinds of methods".

I think the best way to think about the added complexity is to consider the *incremental* complexity and benefit over Swift’s initialization model. I think it is a pretty modest change to understand for those who already understand the existing rules while providing substantial benefit in some cases.

Heh. I don't think Swift's initialization model is exactly the part of the language we want to strive to emulate (even if all its corners are necessary in some sense). This feature doesn't interact much with most of the complexity there except the phase 1 / phase 2 part.

(Specifically on that note: "init func" is probably okay because it's a special kind of func, but I think I would personally prefer to keep the 'partial' keyword for partial inits, even though it's technically unnecessary.)

I would be happy with whatever syntax the core team thinks is best. I like keeping it more concise but don’t mind `partial` if others like that better.

It’s just hard to tell what that is with several people involved. Any recommendations on how to handle this when writing a proposal?

I don't think you'd know for real until it got to review. This list is high enough traffic that there are probably a lot of people who don't read the drafts, including members of the core team. :-/

Jordan


(Brent Royal-Gordon) #12

Aside from that, how do you like the changes in this draft in general?

(I'm also going to address some of Jordan Rose's comments here.)

I'm afraid I still have objections to the basic syntax:

- I'm not a big fan of the calling syntax. The name on the left of the dot looks like it ought to be a type or maybe an object, but it's neither. It's also not parallel to the declaration—you declare `init foo()`, but you call `foo.init()`.
- I don't like the fact that partial inits are distinguished from regular ones solely by whether or not they have a name. This is just not an obvious affordance.

I really think that both of these problems can be avoided by reimagining this feature as a way to make methods that can be called during initialization. That is:

  class Foo {
    var a, b: Int
    let now: NSDate
    
    init func resetValues(i: Int) {
      a = i * 2
      b = i * 4
    }
    
    init(only) func captureMoment() {
      now = NSDate()
    }

    init(i: Int) {
      captureMoment()
      resetValues(i)
    }
  }

An `init(only)` method can initialize constants, but cannot be called after phase one. It would not be possible to make the partial initialization aspect of these calls public; `resetValues(_:)` would look like a normal method, and `captureMoment()` would not appear at all.

Alternatively, we could have the `init` keyword always take a list of the properties that will be initialized. This avoids the body-scanning that Jordan complained about, but it would not make explicit which init methods aren't available after initialization—you'd have to examine the property list and figure out which ones were constants. On the other hand, it at least theoretically permits the initializer aspect of the method to be made public, since the properties it initializes will be explicitly declared in the interface.

  class Foo {
    var a, b: Int
    let now: NSDate
    
    init(a, b) func resetValues(i: Int) {
      a = i * 2
      b = i * 4
    }
    
    init(now) func captureMoment() {
      now = NSDate()
    }

    init(i: Int) {
      captureMoment()
      resetValues(i)
    }
  }

One annoying thing about this syntax is that the property list in `init(foo)` looks like the parameter list in an initializer definition, but it actually means something totally different.

A third option here is to have a separate `initonly` keyword *and* require a declaration of initialized properties. This gives us the explicitness of `init(only)` and the in-declaration property list, but it's a bit ugly.

  class Foo {
    var a, b: Int
    let now: NSDate
    
    init(a, b) func resetValues(i: Int) {
      a = i * 2
      b = i * 4
    }
    
    initonly(now) func captureMoment() {
      now = NSDate()
    }

    init(i: Int) {
      captureMoment()
      resetValues(i)
    }
  }

Presumably you could omit the list of properties to write a side-effect-free helper method. This would have the same capabilities as the current static method approach, but with a more convenient calling syntax.

  class Foo {
    var a, b: Int
    
    private init func double(i: Int) -> Int {
      return i * 2
    }
    
    init(a, b) func resetValues(i: Int) {
      a = double(i)
      b = double(double(i))
    }
    
    ...
  }

As Jordan mentioned, overridability is a problem. I see two choices here:

1. Allow overriding, but don't call the override during phase one initialization.
2. Make `init` imply `final`.

#1 is kind of weird; #2 is very limiting. If we go with #1, we also have to decide if we want to allow `super` calls and how they should be handled during initialization, since they can't actually be executed. Perhaps they would be implicitly ignored, or you would have to make sure they weren't called on initialization using a special syntax:

  class Bar: Foo {
    var c: Int
    
    override init(c) func resetValues(i: Int) {
      if !init {
        super.resetValues(i)
      }
      c = i * 8
    }
    
    override init(i: Int) {
      resetValues(i)
      super.init(i: i)
    }
  }

(On the other hand, perhaps #1 is an argument for your `foo.init()` syntax—it looks like a different kind of call, so you might expect it to have different semantics like not calling the override.)

···

--
Brent Royal-Gordon
Architechies


(Slava Pestov) #13

IIRC mandatory inlining runs after DI. I think Chris didn't want cases where the DI looked wrong but was correct because the called function happened to be inlined—there's no indication at the call site of this.

I don’t think that’s a huge problem though. When we emit a diagnostic about an uninitialized value or use, we could also emit a series of ‘inlined from here’ notes.

We also wouldn't want the other semantics of transparent <https://github.com/apple/swift/blob/master/docs/TransparentAttr.rst>: no debug info,

That sounds unfortunate. Why is debug info with transparent functions any more of an issue than debug info for stuff inlined by the optimizer passes? I’m assuming we emit debug info for the latter.

and restrictions on what can be mentioned if it's marked public.

Arguably, public partial inits only make sense for @_fixed_layout structs anyway :slight_smile:

Jordan

Slava

···

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


(Chris Lattner) #14

Ignoring the fact that @_transparent isn’t a user feature, I don’t see how this helps. Mandatory inlining of @_transparent happens after DI runs.

-Chris

···

On Jan 20, 2016, at 8:51 PM, Slava Pestov via swift-evolution <swift-evolution@swift.org> wrote:

Adding more details…

On Jan 20, 2016, at 8:46 PM, Slava Pestov via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I'm a little concerned about exposing these things publicly: there's no point if the struct or class has non-public fields (because new non-delegating initializers can't initialize the non-public fields), and by default every struct and class is allowed to add non-public fields in new versions of a library. (See the LibraryEvolution.rst doc for more info.) I can kind of see making these private vs. internal, but that has other issues (see below).

Even in the case where all fields are public and the struct promises not to change any fields, you'd also have to promise that the partial initializer never changes which properties it's initializing; that's now part of the binary interface of the file. That's unusual for something that behaves like a function; usually the body is not part of the ABI.

I'd much rather just ban 'public' and never have partial inits be part of a library's public interface. Public initializer methods would be presented as plain methods, with no hint of their initializer origins.

If we just go with @_transparent functions for partial initialization, there are no new rules to add regarding ABI resilience. :-)


(Matthew Johnson) #15

9. The compiler keeps track of the properties initialized by a partial initializer or initialization method and uses that knowledge when enforcing initialization rules in phase one in the calling initializer.

If a struct partial initializer is defined in another file, this would require reading the body of the initializer to determine what variables it initializes. We currently don't type-check any bodies outside of the primary source file when doing a normal, multi-process compilation.

Are you recommending a same-file requirement? I wouldn’t have a problem with that. I’m trying to keep this proposal as focused as possible and will make any changes necessary to address implementation considerations.

That would be one way to solve this, but that's kind of at odds with your direction of "partial initializers for stored properties in extensions”.

I included that discussion at John’s request. He also mentioned that they wouldn’t necessarily share the same implementation even though they are conceptually similar. I don’t think it needs to influence this proposal to the degree that we would exclude that option.

I think a same-file requirement would be a reasonable place to start if the alternative is explicit declaration.

There's also a circularity problem, where type-checking one partial init requires looking at the body of another, which mistakenly calls the first, but we can probably handle that error.

If partial inits calling other partial inits significantly increases complexity I could remove that from the initial proposal. I don’t think it’s an essential feature.

The other way that comes to mind would be to explicitly declare what's being initialized, as Brent (and others?) mentioned. That's certainly not as concise but it does fix the problem.

I don’t like the idea of requiring this to be declared too much. If we had “self.x” syntax and any “self.x” parameters implicitly declared the initialization of their corresponding property maybe it would be tolerable as would only need to explicitly declare properties set manually.

I don't think it counts as a leak of information about stored properties as long as partial inits are never exposed publicly.

Agree.

Can an initializer method be overridden? If so, I assume that does not override the "initializer" part, just the "method" part? If not, can we require the 'final' to be explicit?

Good question. I was thinking of them as implicitly `final` but didn’t call that out. If you think they should be explicitly marked `final` I will make that change.

Well, I think it's a good idea. Others may disagree. It could also be required only for public init methods, because private and internal ones don't make an API contract.

Can the method be marked @objc? 'dynamic'? I would assume "yes", because it's not really different from any other method, but I think it ought to be called out.

I suppose so if that doesn’t create any implementation challenges around the initializer aspect.

Yeah, they wouldn't apply to the initializer, only the method. I don't know if that's weird enough to forbid 'dynamic', but it seems benign for '@objc'. (AFAICT the effects of '@objc' are only observable after phase 1 completes anyway.)

I don't personally like the call syntax but I don't immediately have anything better to offer. I do think that part of this proposal should include naming guidelines for these things.

I don’t love it but don’t have a better idea either. It’s a good candidate for bike shedding and we might be able to find something better.

What do you have in mind in terms of naming guidelines? I can imagine UI classes having partial inits like “appearanceProperties” and “animationProperties”, etc. Those names wouldn’t be good for an initializer method though.

I think that does give us a place to start: initializer methods are named like methods but invoked in some special way. Maybe partial inits are named as methods as well. (Maybe we just drop partial inits to make everything simpler, and only have init methods. They can get dead-code-stripped if they're only used as partial inits.)

If we drop partial inits how would you recommend factoring out initialization logic that touches `let` properties? It seems wrong to allow that in an init method. The only way to support both `let` initialization *and* post-initialization calls is with two kinds of constructs. They could share the same syntax, but that seems confusing to me. That is why I included both of them in the proposal.

What happens when you call an init method from phase 2 of initialization? Is that just illegal?

Good question. I should have addressed this. Partial initializers should not be callable during phase 2.

It probably make senses to allow initializer methods to be called during phase 2 using the usual method syntax just like any other method. What do others think?

I'm inclined to agree.

I'm ultimately still not convinced that this is the right way to reduce initializer boilerplate (phase 1 duplication between initializers, or shared code between initializers and methods). It again adds complexity to the language, and that added complexity may not be worth the payoff. I'm thinking about someone coming across the construct for the first time: would they understand what it was doing? Would they understand the error messages they would get?

What I like about this approach is that it is a general tool for factoring initialization code using techniques similar to those we use in other code. Lots of people do similar things in Objective-C, but in an obviously unsafe way. This would help ease their migration to Swift while helping them do this safely.

I do like the idea of "you can factor out code to helper methods, but initialization code is special, so you factor it out to special kinds of methods".

I think the best way to think about the added complexity is to consider the *incremental* complexity and benefit over Swift’s initialization model. I think it is a pretty modest change to understand for those who already understand the existing rules while providing substantial benefit in some cases.

Heh. I don't think Swift's initialization model is exactly the part of the language we want to strive to emulate (even if all its corners are necessary in some sense). This feature doesn't interact much with most of the complexity there except the phase 1 / phase 2 part.

I didn’t meant this in terms of striving to emulate the initialization model. Just that the feature is intricately related to DI and is wouldn’t be necessary without the phase 1 restrictions. I think of it as a small enhancement to the init capabilities that provides a lot more benefit than it introduces complexity, especially considering how complex the init model already is.

(Specifically on that note: "init func" is probably okay because it's a special kind of func, but I think I would personally prefer to keep the 'partial' keyword for partial inits, even though it's technically unnecessary.)

I would be happy with whatever syntax the core team thinks is best. I like keeping it more concise but don’t mind `partial` if others like that better.

It’s just hard to tell what that is with several people involved. Any recommendations on how to handle this when writing a proposal?

I don't think you'd know for real until it got to review. This list is high enough traffic that there are probably a lot of people who don't read the drafts, including members of the core team. :-/

Sure. But it might be nice to be able to write a proposal “modulo syntax”. We need placeholder syntax of course, but in many cases it might be best to allow that to be hashed out during review with the core team deciding on the final details if the proposal is accepted. There have been several cases where this seems like an appropriate way to handle things. What do you think?

-Matthew

···

On Jan 22, 2016, at 1:12 PM, Jordan Rose <jordan_rose@apple.com> wrote:


(Jordan Rose) #16

IIRC mandatory inlining runs after DI. I think Chris didn't want cases where the DI looked wrong but was correct because the called function happened to be inlined—there's no indication at the call site of this.

I don’t think that’s a huge problem though. When we emit a diagnostic about an uninitialized value or use, we could also emit a series of ‘inlined from here’ notes.

I think it's the other way around: when everything is correct, a new reader will look at the code and say "wait, why are you allowed to pass this inout here?" That applies less to partial initializers, though, which do have some syntax called out. We'd have to check with Chris to be sure, though, since it was a deliberate change at some point in the project's life.

(It's also possible there's a pass ordering problem where we use DI to determine what can be promoted to the stack, or something like that. I'm not too familiar with the mandatory passes.)

We also wouldn't want the other semantics of transparent <https://github.com/apple/swift/blob/master/docs/TransparentAttr.rst>: no debug info,

That sounds unfortunate. Why is debug info with transparent functions any more of an issue than debug info for stuff inlined by the optimizer passes? I’m assuming we emit debug info for the latter.

No, you've got it backwards. This is a feature of transparent. Nobody wants to step into +.

Now, we could break that feature out into a separate attribute, but then there's nothing "transparent" anymore. It's just "inline very early while we still have source locations to do diagnostics".

Obviously, forced inlining can also hurt code size.

and restrictions on what can be mentioned if it's marked public.

Arguably, public partial inits only make sense for @_fixed_layout structs anyway :slight_smile:

But it affects initializer methods, which are just normal methods otherwise.

Jordan

···

On Jan 20, 2016, at 21:10 , Slava Pestov <spestov@apple.com> wrote:

On Jan 20, 2016, at 8:55 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:


(Matthew Johnson) #17

IIRC mandatory inlining runs after DI. I think Chris didn't want cases where the DI looked wrong but was correct because the called function happened to be inlined—there's no indication at the call site of this.

I don’t think that’s a huge problem though. When we emit a diagnostic about an uninitialized value or use, we could also emit a series of ‘inlined from here’ notes.

We also wouldn't want the other semantics of transparent <https://github.com/apple/swift/blob/master/docs/TransparentAttr.rst>: no debug info,

That sounds unfortunate. Why is debug info with transparent functions any more of an issue than debug info for stuff inlined by the optimizer passes? I’m assuming we emit debug info for the latter.

and restrictions on what can be mentioned if it's marked public.

Arguably, public partial inits only make sense for @_fixed_layout structs anyway :slight_smile:

I don’t understand that comment. What is the argument that they only make sense for @_fixed_layout structs? It feels like a generally useful feature to me.

···

On Jan 20, 2016, at 11:10 PM, Slava Pestov <spestov@apple.com> wrote:

On Jan 20, 2016, at 8:55 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

Jordan

Slava


(Matthew Johnson) #18

Aside from that, how do you like the changes in this draft in general?

(I'm also going to address some of Jordan Rose's comments here.)

I'm afraid I still have objections to the basic syntax:

How strong are your objections? Would you vote against this because of the syntax or is it merely a preference for something different?

- I'm not a big fan of the calling syntax. The name on the left of the dot looks like it ought to be a type or maybe an object, but it's neither. It's also not parallel to the declaration—you declare `init foo()`, but you call `foo.init()`.

The thing I like about this syntax is that it emphasizes that initialization is happening and that you are not just making a normal method call.

I do think a reasonable argument can be made to just use normal method syntax. I mention this in the proposal and am willing to use the syntax that most people like best.

- I don't like the fact that partial inits are distinguished from regular ones solely by whether or not they have a name. This is just not an obvious affordance.

That’s a fair opinion. I will re-introduce the `partial` keyword if most people prefer that.

I don’t think using `func` to declare something that writes to `let` properties is a good idea. I very much prefer `init <name>()` or `partial init <name>()`.

I really think that both of these problems can be avoided by reimagining this feature as a way to make methods that can be called during initialization. That is:

  class Foo {
    var a, b: Int
    let now: NSDate
    
    init func resetValues(i: Int) {
      a = i * 2
      b = i * 4
    }
    
    init(only) func captureMoment() {
      now = NSDate()
    }

    init(i: Int) {
      captureMoment()
      resetValues(i)
    }
  }

An `init(only)` method can initialize constants, but cannot be called after phase one. It would not be possible to make the partial initialization aspect of these calls public; `resetValues(_:)` would look like a normal method, and `captureMoment()` would not appear at all.

Alternatively, we could have the `init` keyword always take a list of the properties that will be initialized. This avoids the body-scanning that Jordan complained about, but it would not make explicit which init methods aren't available after initialization—you'd have to examine the property list and figure out which ones were constants. On the other hand, it at least theoretically permits the initializer aspect of the method to be made public, since the properties it initializes will be explicitly declared in the interface.

  class Foo {
    var a, b: Int
    let now: NSDate
    
    init(a, b) func resetValues(i: Int) {
      a = i * 2
      b = i * 4
    }
    
    init(now) func captureMoment() {
      now = NSDate()
    }

    init(i: Int) {
      captureMoment()
      resetValues(i)
    }
  }

One annoying thing about this syntax is that the property list in `init(foo)` looks like the parameter list in an initializer definition, but it actually means something totally different.

A third option here is to have a separate `initonly` keyword *and* require a declaration of initialized properties. This gives us the explicitness of `init(only)` and the in-declaration property list, but it's a bit ugly.

  class Foo {
    var a, b: Int
    let now: NSDate
    
    init(a, b) func resetValues(i: Int) {
      a = i * 2
      b = i * 4
    }
    
    initonly(now) func captureMoment() {
      now = NSDate()
    }

    init(i: Int) {
      captureMoment()
      resetValues(i)
    }
  }

Presumably you could omit the list of properties to write a side-effect-free helper method. This would have the same capabilities as the current static method approach, but with a more convenient calling syntax.

  class Foo {
    var a, b: Int
    
    private init func double(i: Int) -> Int {
      return i * 2
    }
    
    init(a, b) func resetValues(i: Int) {
      a = double(i)
      b = double(double(i))
    }
    
    ...
  }

As Jordan mentioned, overridability is a problem. I see two choices here:

1. Allow overriding, but don't call the override during phase one initialization.
2. Make `init` imply `final`.

I think `init` *has* to imply `final`. We cannot call a subclass method during phase 1, period.

···

On Jan 20, 2016, at 10:57 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

#1 is kind of weird; #2 is very limiting. If we go with #1, we also have to decide if we want to allow `super` calls and how they should be handled during initialization, since they can't actually be executed. Perhaps they would be implicitly ignored, or you would have to make sure they weren't called on initialization using a special syntax:

  class Bar: Foo {
    var c: Int
    
    override init(c) func resetValues(i: Int) {
      if !init {
        super.resetValues(i)
      }
      c = i * 8
    }
    
    override init(i: Int) {
      resetValues(i)
      super.init(i: i)
    }
  }

(On the other hand, perhaps #1 is an argument for your `foo.init()` syntax—it looks like a different kind of call, so you might expect it to have different semantics like not calling the override.)

--
Brent Royal-Gordon
Architechies


(Slava Pestov) #19

Adding more details…

I'm a little concerned about exposing these things publicly: there's no point if the struct or class has non-public fields (because new non-delegating initializers can't initialize the non-public fields), and by default every struct and class is allowed to add non-public fields in new versions of a library. (See the LibraryEvolution.rst doc for more info.) I can kind of see making these private vs. internal, but that has other issues (see below).

Even in the case where all fields are public and the struct promises not to change any fields, you'd also have to promise that the partial initializer never changes which properties it's initializing; that's now part of the binary interface of the file. That's unusual for something that behaves like a function; usually the body is not part of the ABI.

I'd much rather just ban 'public' and never have partial inits be part of a library's public interface. Public initializer methods would be presented as plain methods, with no hint of their initializer origins.

If we just go with @_transparent functions for partial initialization, there are no new rules to add regarding ABI resilience. :slight_smile:

Ignoring the fact that @_transparent isn’t a user feature, I don’t see how this helps. Mandatory inlining of @_transparent happens after DI runs.

I’m of the opinion that making it a user feature and running inlining before DI would solve the ‘partial initializers’ use-case. :slight_smile:

···

On Jan 21, 2016, at 9:13 PM, Chris Lattner <clattner@apple.com> wrote:

On Jan 20, 2016, at 8:51 PM, Slava Pestov via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 20, 2016, at 8:46 PM, Slava Pestov via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

-Chris


(Chris Lattner) #20

Sorry, I didn’t see that Jordan brought this up down-thread: He’s exactly right. We don’t *want* mandatory inlining to run before DI (it used to, and it was a mess). Doing that just makes the DI model more complex and less predictable for users.

I’m not sold on the notion of partial initializers in the first place, but if we were to go down this path, the approach of nailing down the exact initialization behavior and requirements of the partial initializer - and making it part of its explicitly declared interface - is the right way to go.

-Chris

···

On Jan 21, 2016, at 9:13 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 20, 2016, at 8:51 PM, Slava Pestov via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Adding more details…

On Jan 20, 2016, at 8:46 PM, Slava Pestov via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I'm a little concerned about exposing these things publicly: there's no point if the struct or class has non-public fields (because new non-delegating initializers can't initialize the non-public fields), and by default every struct and class is allowed to add non-public fields in new versions of a library. (See the LibraryEvolution.rst doc for more info.) I can kind of see making these private vs. internal, but that has other issues (see below).

Even in the case where all fields are public and the struct promises not to change any fields, you'd also have to promise that the partial initializer never changes which properties it's initializing; that's now part of the binary interface of the file. That's unusual for something that behaves like a function; usually the body is not part of the ABI.

I'd much rather just ban 'public' and never have partial inits be part of a library's public interface. Public initializer methods would be presented as plain methods, with no hint of their initializer origins.

If we just go with @_transparent functions for partial initialization, there are no new rules to add regarding ABI resilience. :-)

Ignoring the fact that @_transparent isn’t a user feature, I don’t see how this helps. Mandatory inlining of @_transparent happens after DI runs.