Why is Self not static in reference types?

I have always thought that Self was static in reference types, regardless of whether a type has sub-types or not, and that it was always interchangeable with the name of the type.

class U {
    func f (_ u: AnyObject) {
        // Don't hardwire `U` but use `Self` instead
        guard let v = u as? Self else {
            print (#function, "expecting U", "got", Self.self)
            return
        }
        g (v)
    }
    
    // Subclasses to override
    func g (_ u: U) {
        print (#function, Self.self)
    }
}

But, to my great surprise, I have discovered that this is not the case at all when a type has sub-types. Below, simply changing the typealias in U.f changes the output of the test.

@main
enum ReferenceTypes {
    static func main () {
        let u = V ()
        u.f (u)
        
        let v = W ()
        u.f (v)
        v.f (u)
    }
}

class U {
    func f (_ u: AnyObject) {
        typealias T = Self
        // typealias T = U
        guard let v = u as? T else {
           print (#function, "expecting U", "got", Self.self)
           return
        }
        g (v)
    }
    
    // Subclasses to override
    func g (_ u: U) {
        print (#function, Self.self)
    }
}

class V: U {
    override func g (_ u: U) {
        print (#function, Self.self)
    }
}

class W: U {
    override func g (_ u: U) {
        print (#function, Self.self)
    }
}

With typealias T = U in U.f, the output is as I expect it.

// typealias T = U in U.f
g(_:) V
g(_:) V
g(_:) W

However, with typealias T = Self in U.f, the output is not as I expect it.

// typealias T = Self in U.f
g(_:) V
f(_:) expecting U got V
f(_:) expecting U got W

Can anyone explain the reason behind this?

See:

5 Likes

This looks logical, otherwise how would you get to the actual type? Self gives you the actual type (V or W in your example) and U gives you U.

2 Likes

I now understand the dynamic nature of Self in reference types. :slight_smile:

However, I couldn't concoct a better example that demonstrates how useful it can be in practical applications. :thinking:

class X: CustomStringConvertible {
    let u: Int
    
    required init (u: Int = 2) {
        self.u = u
    }
    
    func f (u: Int) -> Self {
        #if false
            return Self.init (v: u, u: 2 * u + 1) // Extra argument 'v' in call
        #else
            return Self.init (u: u)
        #endif
    }
    
    var description: String {
        "\(type (of: self)) (\(u))"
    }
}

class Y: X {
    let v: Int
    required init (u: Int = 3) {
        self.v = u
        super.init (u: 2 * u + 1)
    }
    
    required init (v: Int, u: Int) {
        self.v = v
        super.init (u: u)
    }
    
    override var description: String {
        "\(type (of: self)) (\(u), \(v))"
    }
}

let x = X ()
print (x.f (u: 11))

let y = Y ()
print (y.f (u: 13))

/*
// Prints

X (11)
Y (27, 13)
*/

I always thought it's for static factory methods, like

class Foo {
  required init(x: Array<Whatever>) { ... }

  class var empty: Self { Self.init(x: []) }
}
3 Likes

Or the mutable OO programmer's favorite, an instance method that clones the instance:

class Shape {
  func clone() -> Self { ... }
}

class Rectangle: Shape {...}

let rect = Rectangle(...)
let rect2 = rect.clone()
3 Likes