Pitch: Restrict Cross-module Struct Initializers

While working on the non-exhaustive enums proposal I had it pointed out to me that structs actually currently leak implementation details across module boundaries, specifically their full set of stored properties. This only comes up in one place: when making an initializer for the struct in an extension in another module. We really want people to be able to change the stored properties in their structs between releases without it being a source break—that's half the point of computed properties—and it's also important for a struct author to be able to enforce invariants across the struct's properties. So after talking to a few other Apple Swift folks I put together this proposal:

https://github.com/jrose-apple/swift-evolution/blob/restrict-cross-module-struct-initializers/proposals/nnnn-restrict-cross-module-struct-initializers.md

This one's way smaller than the enum one, and hopefully fairly uncontroversial. Feedback welcome!

Jordan

Yes, this feels right. I was a little unsure/unclear about this at first,
but the example with `let` and invariants convinced me, and now I can see
how changing a stored property to a computed property later could cause
problems in your scenario.

So even though *technically* there is some subset of structs (PODs, mainly)
where creating extra-module initializers could feasibly work, I think it
makes complete sense to align with classes here because what we want to say
is that "the only person who knows enough about a type to initialize it is
the module who defines it; anyone else should have to go through them to
get an instance".

Strong +1 from me.

···

On Fri, Oct 6, 2017 at 2:32 PM Jordan Rose via swift-evolution < swift-evolution@swift.org> wrote:

While working on the non-exhaustive enums proposal I had it pointed out to
me that *structs* actually currently leak implementation details across
module boundaries, specifically their full set of stored properties. This
only comes up in one place: when making an initializer for the struct in an
extension in another module. We really want people to be able to change the
stored properties in their structs between releases without it being a
source break—that's half the point of computed properties—and it's also
important for a struct author to be able to enforce invariants across the
struct's properties. So after talking to a few other Apple Swift folks I
put together this proposal:

https://github.com/jrose-apple/swift-evolution/blob/restrict-cross-module-struct-initializers/proposals/nnnn-restrict-cross-module-struct-initializers.md

This one's way smaller than the enum one, and hopefully fairly
uncontroversial. Feedback welcome!

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

Presumably, @_fixed_layout and its future formalized cousin would restore
the current functionality?

···

On Fri, Oct 6, 2017 at 16:32 Jordan Rose via swift-evolution < swift-evolution@swift.org> wrote:

While working on the non-exhaustive enums proposal I had it pointed out to
me that *structs* actually currently leak implementation details across
module boundaries, specifically their full set of stored properties. This
only comes up in one place: when making an initializer for the struct in an
extension in another module. We really want people to be able to change the
stored properties in their structs between releases without it being a
source break—that's half the point of computed properties—and it's also
important for a struct author to be able to enforce invariants across the
struct's properties. So after talking to a few other Apple Swift folks I
put together this proposal:

https://github.com/jrose-apple/swift-evolution/blob/restrict-cross-module-struct-initializers/proposals/nnnn-restrict-cross-module-struct-initializers.md

This one's way smaller than the enum one, and hopefully fairly
uncontroversial. Feedback welcome!

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

Great catch, +1 to the proposal!

Please add the point Xiodi mentions to the writing though so that the review cycle adequately discusses it. A "fragile struct” (for some definition of fragility), but definitely including C structs, will have knowable stored properties, and it isn’t clear that these should be subjected to this restriction. The win of the restriction in that case is the invariant point you’re making. It is best to address this head-on in the writing, and mention the alternate approach in the ‘alternatives considered’ section (along with why you think you’ve picked the right choice).

-Chris

···

On Oct 6, 2017, at 2:32 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

While working on the non-exhaustive enums proposal I had it pointed out to me that structs actually currently leak implementation details across module boundaries, specifically their full set of stored properties. This only comes up in one place: when making an initializer for the struct in an extension in another module. We really want people to be able to change the stored properties in their structs between releases without it being a source break—that's half the point of computed properties—and it's also important for a struct author to be able to enforce invariants across the struct's properties. So after talking to a few other Apple Swift folks I put together this proposal:

https://github.com/jrose-apple/swift-evolution/blob/restrict-cross-module-struct-initializers/proposals/nnnn-restrict-cross-module-struct-initializers.md

This one's way smaller than the enum one, and hopefully fairly uncontroversial. Feedback welcome!

Good question. Slava and I talked about that and decided that there wasn't much point. '_fixed_layout' still doesn't cover the invariant problem, and memberwise initializers are really common when that's not relevant. We don't think there should ever be a reason to write _fixed_layout in a source package, and we wouldn't want this to become one.

Jordan

···

On Oct 6, 2017, at 15:23, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Presumably, @_fixed_layout and its future formalized cousin would restore the current functionality?

On Fri, Oct 6, 2017 at 16:32 Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
While working on the non-exhaustive enums proposal I had it pointed out to me that structs actually currently leak implementation details across module boundaries, specifically their full set of stored properties. This only comes up in one place: when making an initializer for the struct in an extension in another module. We really want people to be able to change the stored properties in their structs between releases without it being a source break—that's half the point of computed properties—and it's also important for a struct author to be able to enforce invariants across the struct's properties. So after talking to a few other Apple Swift folks I put together this proposal:

https://github.com/jrose-apple/swift-evolution/blob/restrict-cross-module-struct-initializers/proposals/nnnn-restrict-cross-module-struct-initializers.md

This one's way smaller than the enum one, and hopefully fairly uncontroversial. Feedback welcome!

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

Oh, sorry, the missing piece of that is 'inlinable': an inlinable memberwise initializer in a fixed-layout struct should have no performance overhead in an optimized build.

Jordan

···

On Oct 6, 2017, at 15:25, Jordan Rose <jordan_rose@apple.com> wrote:

Good question. Slava and I talked about that and decided that there wasn't much point. '_fixed_layout' still doesn't cover the invariant problem, and memberwise initializers are really common when that's not relevant. We don't think there should ever be a reason to write _fixed_layout in a source package, and we wouldn't want this to become one.

Jordan

On Oct 6, 2017, at 15:23, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

Presumably, @_fixed_layout and its future formalized cousin would restore the current functionality?

On Fri, Oct 6, 2017 at 16:32 Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
While working on the non-exhaustive enums proposal I had it pointed out to me that structs actually currently leak implementation details across module boundaries, specifically their full set of stored properties. This only comes up in one place: when making an initializer for the struct in an extension in another module. We really want people to be able to change the stored properties in their structs between releases without it being a source break—that's half the point of computed properties—and it's also important for a struct author to be able to enforce invariants across the struct's properties. So after talking to a few other Apple Swift folks I put together this proposal:

https://github.com/jrose-apple/swift-evolution/blob/restrict-cross-module-struct-initializers/proposals/nnnn-restrict-cross-module-struct-initializers.md

This one's way smaller than the enum one, and hopefully fairly uncontroversial. Feedback welcome!

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

This is a great proposal.

However, it can make unit testing problematic. Let me explain.

In my unit tests, I often add initializers that set stored properties directly to create structs with desired data and those are then fed into the struct that’s under test. This is to test in complete isolation, without invoking the “production” initializer of structs that aren’t under test. In other words, I want to avoid invoking any behavior of structs that aren’t directly under test.

Would the rules described in this proposal be enforced also for testing targets/modules?

R+

···

Sent from my iPhone

On 7 Oct 2017, at 23:44, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Oct 6, 2017, at 2:32 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

While working on the non-exhaustive enums proposal I had it pointed out to me that structs actually currently leak implementation details across module boundaries, specifically their full set of stored properties. This only comes up in one place: when making an initializer for the struct in an extension in another module. We really want people to be able to change the stored properties in their structs between releases without it being a source break—that's half the point of computed properties—and it's also important for a struct author to be able to enforce invariants across the struct's properties. So after talking to a few other Apple Swift folks I put together this proposal:

https://github.com/jrose-apple/swift-evolution/blob/restrict-cross-module-struct-initializers/proposals/nnnn-restrict-cross-module-struct-initializers.md

This one's way smaller than the enum one, and hopefully fairly uncontroversial. Feedback welcome!

Great catch, +1 to the proposal!

Please add the point Xiodi mentions to the writing though so that the review cycle adequately discusses it. A "fragile struct” (for some definition of fragility), but definitely including C structs, will have knowable stored properties, and it isn’t clear that these should be subjected to this restriction. The win of the restriction in that case is the invariant point you’re making. It is best to address this head-on in the writing, and mention the alternate approach in the ‘alternatives considered’ section (along with why you think you’ve picked the right choice).

-Chris

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

Good call. Updated the Alternatives Considered section with my response to Xiaodi, plus a specific section on C structs. (Also added the @testable part that Rudolf mentioned.)

Initial implementation has also been posted.

Jordan

···

On Oct 7, 2017, at 14:44, Chris Lattner <clattner@nondot.org> wrote:

On Oct 6, 2017, at 2:32 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

While working on the non-exhaustive enums proposal I had it pointed out to me that structs actually currently leak implementation details across module boundaries, specifically their full set of stored properties. This only comes up in one place: when making an initializer for the struct in an extension in another module. We really want people to be able to change the stored properties in their structs between releases without it being a source break—that's half the point of computed properties—and it's also important for a struct author to be able to enforce invariants across the struct's properties. So after talking to a few other Apple Swift folks I put together this proposal:

https://github.com/jrose-apple/swift-evolution/blob/restrict-cross-module-struct-initializers/proposals/nnnn-restrict-cross-module-struct-initializers.md

This one's way smaller than the enum one, and hopefully fairly uncontroversial. Feedback welcome!

Great catch, +1 to the proposal!

Please add the point Xiodi mentions to the writing though so that the review cycle adequately discusses it. A "fragile struct” (for some definition of fragility), but definitely including C structs, will have knowable stored properties, and it isn’t clear that these should be subjected to this restriction. The win of the restriction in that case is the invariant point you’re making. It is best to address this head-on in the writing, and mention the alternate approach in the ‘alternatives considered’ section (along with why you think you’ve picked the right choice).

Thanks for bringing this up. I thought of this only after posting the proposal, and I think the answer ought to be "you're exempt from this restriction if you use `@testable import`". I still want to work out that this is implementable even for future frameworks with binary compatibility concerns (though the testable-only parts don't have to be a binary-compatible interface), but you're right that the proposal isn't really complete without that exception. I'll add it today.

Jordan

···

On Oct 8, 2017, at 12:49, Rudolf Adamkovič via swift-evolution <swift-evolution@swift.org> wrote:

This is a great proposal.

However, it can make unit testing problematic. Let me explain.

In my unit tests, I often add initializers that set stored properties directly to create structs with desired data and those are then fed into the struct that’s under test. This is to test in complete isolation, without invoking the “production” initializer of structs that aren’t under test. In other words, I want to avoid invoking any behavior of structs that aren’t directly under test.

Would the rules described in this proposal be enforced also for testing targets/modules?

R+

Sent from my iPhone

On 7 Oct 2017, at 23:44, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Oct 6, 2017, at 2:32 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

While working on the non-exhaustive enums proposal I had it pointed out to me that structs actually currently leak implementation details across module boundaries, specifically their full set of stored properties. This only comes up in one place: when making an initializer for the struct in an extension in another module. We really want people to be able to change the stored properties in their structs between releases without it being a source break—that's half the point of computed properties—and it's also important for a struct author to be able to enforce invariants across the struct's properties. So after talking to a few other Apple Swift folks I put together this proposal:

https://github.com/jrose-apple/swift-evolution/blob/restrict-cross-module-struct-initializers/proposals/nnnn-restrict-cross-module-struct-initializers.md

This one's way smaller than the enum one, and hopefully fairly uncontroversial. Feedback welcome!

Great catch, +1 to the proposal!

Please add the point Xiodi mentions to the writing though so that the review cycle adequately discusses it. A "fragile struct” (for some definition of fragility), but definitely including C structs, will have knowable stored properties, and it isn’t clear that these should be subjected to this restriction. The win of the restriction in that case is the invariant point you’re making. It is best to address this head-on in the writing, and mention the alternate approach in the ‘alternatives considered’ section (along with why you think you’ve picked the right choice).

-Chris

_______________________________________________
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