[swift-evolution] [Pitch] `let` in protocols

Robert Bennett rltbennett at icloud.com
Sun Jun 25 15:13:23 CDT 2017


For a concrete type, a partial initializer is a function that:
Can set `let` instance variables
May not refer to `self` in an rvalue
Must set the same subset of instance variables regardless of the code path taken

For a protocol, a partial initializer is simply a function that sets *any* subset of the instance variables. A protocol could provide a default implementation of one. In order for this `let` proposal to work, though, if a `let` variable is set in the protocol extension’s implementation of a (full) initializer, the protocol would need to provide at least one partial initializer that it does not provide an implementation for, so that a conforming type can fill in the gaps for all of the instance variables it defines that the protocol doesn’t require/know about. That way the compiler can have faith that a conforming type will be able to fully initialize itself with the default implementation of the required full initializer.


> On Jun 25, 2017, at 3:57 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
> 
> So your proposed definition of a partial initializer is one that does not set any protocol requirement?
> On Sun, Jun 25, 2017 at 14:54 Robert Bennett <rltbennett at icloud.com <mailto:rltbennett at icloud.com>> wrote:
> The compiler would only raise an error when a conforming type violated the requirement that the partial initializer not set x. The protocol itself would never fail to compile.
> 
> On Jun 25, 2017, at 3:33 PM, Xiaodi Wu <xiaodi.wu at gmail.com <mailto:xiaodi.wu at gmail.com>> wrote:
> 
>> On Sun, Jun 25, 2017 at 2:24 PM, Robert Bennett via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> You’re right Xiaodi, I did not entirely think this through. However I think this could still work with another addition to the language: partial initializers. These would be treated as initializers are now (meaning, for instance, no reference to self until initialization is complete) except they would only need to initialize a subset of the instance variables before exiting. An initializer could then satisfy the requirement that all instance variables must be initialized by calling partial initializers.
>> 
>> By incorporating partial initializers, it would be possible to guarantee that a `let` variable in a protocol is only set once. This is because an init implemented in a protocol extension could delegate to a partial initializers required by the protocol, and because they are only partial initializers, they need not set the instance variables already set in the initializer; it would be up to a conforming type to ensure this is the caee. A confirming type would define the partial initializer to set everything not already set in the protocol extension’s init, and it would be a compiler error to set a variable in the partial initializer that is already set in the extension’s init without overriding the extension’s init.
>> 
>> How can the compiler raise this error when the implementation of the partial initializer does not even have to exist at compile time?
>> 
>> Example code:
>> 
>> protocol P {
>>     var x: Int { let } // or { get set(init) }, or whatever
>> 
>>     partialinit initializeRest()
>>     init()
>> }
>> 
>> extension P {
>>     init() {
>>         initializeRest()
>> 
>> How can the compiler ensure that `initializeRest()` does not already set `x`?
>>  
>>         self.x = 1
>>     }
>> }
>> 
>> struct S: P {
>>     let x: Int
>>     var y: String
>> 
>>     // It would be a compiler error to set x here without also redefining init()
>>     partialinit initializeRest() {
>>         self.y = “P has no knowledge of me”
>>     }
>> 
>>     // Can use default init provided by P
>> }
>> 
>> > On Jun 23, 2017, at 8:12 PM, Karl Wagner <razielim at gmail.com <mailto:razielim at gmail.com>> wrote:
>> >
>> > What you want is some way to guarantee value semantics when writing generic code.
>> >
>> > It’s a known hole, and admittedly quite a big one. I hope that there will be time for core language improvements like this in Swift 5. Be sure to raise the issue again once planning for that starts!
>> >
>> > - Karl
>> >
>> >> On 23. Jun 2017, at 23:43, Robert Bennett via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> >>
>> >> Hello Swift Evolution,
>> >>
>> >> I’m bumping into an annoying problem with protocols. In a class or struct it is common to have a `let` instance variable and assign it in `init`. Unfortunately there is no way to translate this into a protocol with init in an extension. If attempting to set the variable in init in an extension, it must be of type { get set }, which means it cannot be a `let` constant in the conforming type. AFAIK there is no way around this — if you want to set an instance variable in an initializer in a protocol extension, it must be marked as { get set }. The alternative is to write the initializer separately for each adopting type, but this violates DRY.
>> >>
>> >> Hence, I am proposing a third option to go along with `get` and `set` in a protocol. This would indicate that the variable can be a constant, but is settable in an initializer. In this case, the conforming type *must* use `let` to declare the variable.
>> >>
>> >> Option 1: the keyword `let`. If present, it would need to be the only thing in the curly brackets because it simultaneously implies `get` and not `set`.
>> >>
>> >> protocol P {
>> >>   var x: Int { let }
>> >>   init(_ x: Int)
>> >>   func modifyX()
>> >> }
>> >> extension P {
>> >>   init(_ x: Int) {
>> >>       self.x = x // This is ok; would not be ok if x were marked { get }
>> >>   }
>> >>
>> >>   func modifyX() {
>> >>       self.x += 1 // Not allowed
>> >>   }
>> >> }
>> >>
>> >> struct S: P {
>> >>   let x: Int // This is ok; would not be ok if x were marked { get set }
>> >> }
>> >>
>> >> Option 2: `set(init)`. Can (and often will) coexist with `get`.
>> >>
>> >> protocol P {
>> >>   var x: Int { get set(init) }
>> >>   init(_ x: Int)
>> >>   func modifyX()
>> >> }
>> >> extension P {
>> >>   init(_ x: Int) {
>> >>       self.x = x // This is ok; would not be ok if x were marked { get }
>> >>   }
>> >>
>> >>   func modifyX() {
>> >>       self.x += 1 // Not allowed
>> >>   }
>> >> }
>> >>
>> >> struct S: P {
>> >>   let x: Int // This is ok; would not be ok if x were marked { get set }
>> >> }
>> >>
>> >>
>> >> I’d like to hear all of your thoughts on this.
>> >>
>> >> Best,
>> >> Robert
>> >> _______________________________________________
>> >> swift-evolution mailing list
>> >> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> >> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> >
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170625/3ebca977/attachment.html>


More information about the swift-evolution mailing list