Skip to content

Instantly share code, notes, and snippets.

@erica
Last active May 18, 2016 14:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erica/995af96a0de2f2f3dc419935e8140927 to your computer and use it in GitHub Desktop.
Save erica/995af96a0de2f2f3dc419935e8140927 to your computer and use it in GitHub Desktop.

Introducing StaticSelf, an Invariant Self

Introduction

This proposal introduces a new keyword that provides consistent invariant type semantics in all contexts.

The Swift-evolution thread about this topic can be found here: [RFC] #Self

Motivation

The distinction between covariant and non-covariant type references come into play when conforming non-final classes to protocols. Fixing a protocol requirement to a covarying type means that a method returning Self must be overriden by all subclasses in order to return the correct, matching type.

This proposal builds on the covariant construct Self accepted in SE-0068 to introduce an invariant type identifier. It enables protocol declarations to consistently refer to a type that is fixed at compile time. This ensures that subclasses can inherit protocol implementations without having to re-implement that code at each level of inheritance.

Under this proposal, a new identifier keyword is fixed in use at the point of protocol conformance to the static type of that construct.

class A: MyProtocol

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 { 
     // ...
 }
}

Additional Utility

The utility of StaticSelf is not limited to protocols. A secondary use enables code to refer to the lexical context's current type without explicitly mentioning its name. This provides a useful shortcut when referencing static type members with especially long names and when re-purposing code between types.

class StructWithAVeryLongName {
    static func foo() -> String {
      // ...
    }
    func bar() {
      // ...
      let s = StaticSelf.foo()
      //
    }
}

Detailed Design

This proposal introduces StaticSelf, a new keyword that may be used in protocols to refer to the invariant static type of a conforming construct. StaticSelf may also be used in the lexical context of any type declaration. In such use, the keyword is identical to spelling out the full name of that type.

Impact on existing code

Being additive, there should be no impact on existing code.

Alternatives considered

The keyword is not fixed at this time. Alternatives that have been discussed include StaticType, InvariantSelf, SelfType, or Type. The community is welcome to bikeshed on the most clear and concise name for this keyword.

Resolution

Nicola Salmoria pointed out that you can use an associated type to work around this limitation without updating Swift:

// This version will not work
/*
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'
}
*/

// This version works in current Swift

protocol StringInitializable {
    associatedtype Initialized = Self // where Self: Initialized
    static func initializeWith(string: String) -> Initialized
}

extension NSURL: StringInitializable {
    // No Errors
    static func initializeWith(string: String) -> NSURL {
        return NSURL(string: string) ?? NSURL()
    }
}

There are two minor downsides to this approach:

  1. You can’t copy and paste the method signature.
  2. You can theoretically conform a type completely unrelated to Initialized to the protocol, thus violating the semantics.

Now that a reasonable way to do this with existing language features has been identified, we withdraw this proposal. If this approach doesn’t address use cases others have in mind for StaticSelf please contact the proposal authors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment