Use partially initialized `self` (likely a bug)

The following code is perfectly valid. Before you run it, take a guess, WHAT IS IT?

class A: NSObject {
    let x = withExtendedLifetime(self) { whatIsIt in
        return whatIsIt
    }
}

print(A().x)

Hint: A must inherit NSObject, or you get build time error (as expected).

What the hell is it

The self above is not really "self". It refers to instance method NSObjectProtocol.self.

Sorry I trolled you. This thread is not about "partially initialized self" at all.

Should we ban it? It's reasonable in a sense, but extremely misleading. Someone could accidentally write something like this:

class MyViewController: UIViewController {
    let button: UIButton = {
        let button = UIButton()
        button.addTarget(self, action: #selector(...), for: .touchUpInside)
        return button
    }()
}

Xcode also render it as a keyword.

Fascinating. -dump-ast shows it clear as day:

(dot_syntax_base_ignored type='(A) -> () -> A' location=./example.swift:3:34 range=[./example.swift:3:34 - line:3:34]
  (type_expr implicit type='A.Type' location=./example.swift:3:34 range=[./example.swift:3:34 - line:3:34] typerepr='A')
  (declref_expr type='(A) -> () -> A' location=./example.swift:3:34 range=[./example.swift:3:34 - line:3:34] decl=Foundation.(file).NSObject.self() function_ref=unapplied))

I would expect self here to refer to the metatype instance (the object representing type A); I’m not sure why it doesn’t.

If only x is static:

class A {
    static var x: A.Type {
        return self
    }
}

I'd expect self to refer to self instance, like this:

class A: NSObject {
    lazy var x = withExtendedLifetime(self) { whatIsIt in
        // We get fully initialized self here because x is lazy.
        return whatIsIt
    }
}

let a = A()
a === a.x // true

Curious indeed. We don’t even need any trickery with closures to surface the behavior; it’s just tied to the meaning of self in that context:

class A: NSObject {
    let x = self
}

A().x  // (__lldb_expr_3.A) -> () -> __lldb_expr_3.A
1 Like

Reference from Swift Programming guide:

If you use a closure to initialize a property, remember that the rest of the instance has not yet been initialized at the point that the closure is executed. This means that you cannot access any other property values from within your closure, even if those properties have default values. You also cannot use the implicit self property, or call any of the instance’s methods.

You can not use self in the initialization.

But the fact is that we can, and it compiles. That's the point of this post.

1 Like

Is that maybe this?

public protocol NSObjectProtocol {
  func `self`() -> Self
}

self is basically an unapplied reference to () -> Self in our case.

1 Like