[swift-evolution] [Draft] Introducing StaticSelf, an Invariant Self

Joe Groff jgroff at apple.com
Fri May 13 11:26:17 CDT 2016


> On May 13, 2016, at 9:22 AM, Matthew Johnson <matthew at anandabits.com> wrote:
> 
> 
> 
> Sent from my iPad
> 
>> On May 13, 2016, at 11:08 AM, Joe Groff <jgroff at apple.com> wrote:
>> 
>> 
>>> On May 13, 2016, at 9:06 AM, Matthew Johnson <matthew at anandabits.com> wrote:
>>> 
>>> 
>>>>> On May 13, 2016, at 10:39 AM, Joe Groff <jgroff at apple.com> wrote:
>>>>> 
>>>>> 
>>>>> On May 13, 2016, at 8:18 AM, Matthew Johnson <matthew at anandabits.com> wrote:
>>>>> 
>>>>> 
>>>>> 
>>>>> Sent from my iPad
>>>>> 
>>>>>> On May 12, 2016, at 9:21 PM, Joe Groff <jgroff at apple.com> wrote:
>>>>>> 
>>>>>> 
>>>>>>> On May 12, 2016, at 5:49 PM, Matthew Johnson via swift-evolution <swift-evolution at swift.org> wrote:
>>>>>>> 
>>>>>>> The invariant StaticSelf identifier will always refer to A, unlike Self, which is covarying and refers to
>>>>>>> the type of the actual instance. Since multiple inheritance for non-protocol types is disallowed,
>>>>>>> this establishes this invariant type identifier with no possibility for conflict.
>>>>>>> 
>>>>>>> Consider the following example, under the current system:
>>>>>>> 
>>>>>>> protocol StringCreatable 
>>>>>>> {
>>>>>>> 
>>>>>>> static func createWithString(s: String) -> Self
>>>>>>> 
>>>>>>> }
>>>>>>> 
>>>>>>> 
>>>>>>> extension NSURL: StringCreatable 
>>>>>>> {
>>>>>>> 
>>>>>>> // cannot conform because NSURL is non-final
>>>>>>> 
>>>>>>> 
>>>>>>> // error: method 'createWithString' in non-final class 'NSURL' must return `Self` to conform to protocol 'A'
>>>>>>> 
>>>>>>> }
>>>>>>> 
>>>>>>> Introducing a static, invariant version of Self permits the desired conformance:
>>>>>>> 
>>>>>>> protocol StringCreatable 
>>>>>>> {
>>>>>>> 
>>>>>>> static func createWithString(s: String) -> StaticSelf
>>>>>>> 
>>>>>>> }
>>>>>>> 
>>>>>>> 
>>>>>>> extension NSURL: StringCreatable 
>>>>>>> {
>>>>>>> 
>>>>>>> // can now conform conform because NSURL is fixed and matches the static
>>>>>>> 
>>>>>>> 
>>>>>>> // type of the conforming construct. Subclasses need not re-implement
>>>>>>> 
>>>>>>> 
>>>>>>> // NOTE: the return type can be declared as StaticSelf *or* as NSURL
>>>>>>> 
>>>>>>> 
>>>>>>> //       they are interchangeable
>>>>>>> 
>>>>>>> 
>>>>>>> static func createWithString(s: String) -> StaticSelf
>>>>>>> { 
>>>>>>> 
>>>>>>> // ...
>>>>>>> 
>>>>>>> }
>>>>>>> }
>>>>>> 
>>>>>> As I've noted before, I don't think this makes sense to encode in the protocol. `Self` is already effectively invariant within a protocol.
>>>>> 
>>>>> 'Self' is not invariant when used as a return type so I'm not sure what you mean.
>>>> 
>>>> That's a property of a conformance. Class conformances are inherited, so they have to satisfy a protocol's requirements . Within the protocol definition, or in a protocol extension, `Self` is a generic parameter bound to a specific conforming type. When you conform a base class to a type, that means `Self == Base` and `Self == Derived` must be possible, but `Self` is never simultaneously `Base` and `Derived`.
>>>> 
>>>>>> If a protocol doesn't have the foresight to use StaticSelf, then you still have the same problems retroactively conforming class hierarchies to the protocol.
>>>>> 
>>>>> True, but in many use cases we are in control of the protocol.  This has always been the case when I have personally encountered this problem.
>>>> 
>>>> That will likely change once resilient Swift frameworks start to exist. Changing `Self` to `StaticSelf` or back would also be a nonresilient change, so if frameworks get this wrong, they wouldn't be able to fix it without breaking ABI, which makes this even more problematic.
>>> 
>>> You can say this about many things.  It seems less problematic than having no way to express this.  Frameworks would not be required to use it if the authors have concerns about ABI.
>> 
>> In this particular case, making conformances non-inheritable, under the conformer's control, would avoid the issue of the framework having to anticipate its users' needs.
> 
> How would that work for class clusters?  I may have an instance of a subclass but only know about the visible superclass.  When the visible superclass conforms to a protocol I expect that the instance I have a reference to also conforms, regardless of its dynamic type.  

In most cases, the subtype conversion should still kick in, so that we can convert ImplSubclass to VisibleSuperclass and invoke a generic with T == VisibleSuperclass instead of with T == ImplSubclass. There might be problems if the ImplSubclass appeared structurally in an invariant position, where implicit conversion is rare, but for situations where you want invariant protocol conformances, such as class clusters, it's rare to work to specific implementation classes directly, since the visible superclass is the primary API interface.

-Joe


More information about the swift-evolution mailing list