Swift lazy instantiating using self
Asked Answered
S

1

15

I have something that really puzzles me, specifically the following code triggers a compiler error "unresolved identifier self", and I am not sure why this is happening, as lazy means that at the time the property will be used, the class is already instantiated. Am I missing something?

Many thanks in advance.

Here is the code

class FirstClass {
    unowned var second: SecondClass

    init(second:SecondClass) {
        self.second = second
        print("First reporting for duty")
    }

    func aMethod() {
        print("First's method reporting for duty")
    }
}


class SecondClass {

    lazy var first = FirstClass(second: self)

    func aMethod() {
        first.aMethod()
    }
}
Sideward answered 30/6, 2016 at 9:1 Comment(2)
What is it exactly that you want to do? Call aMethod from FirstClass in your SecondClass?Ralli
No, just lazily instantiate it, ignore the methodsSideward
P
20

For some reason, a lazy property needs an explicit type annotation if its initial value refers to self. This is mentioned on the swift-evolution mailing list, however I cannot explain why that is necessary.

With

lazy var first: FirstClass = FirstClass(second: self)
//            ^^^^^^^^^^^^

your code compiles and runs as expected.

Here is another example which demonstrates that the problem occurs also with structs, i.e. it is unrelated to subclassing:

func foo(x: Int) -> Int { return x + 1 }

struct MyClass {
    let x = 1

    lazy var y = foo(0)            // No compiler error
    lazy var z1 = foo(self.x)      // error: use of unresolved identifier 'self'
    lazy var z2: Int = foo(self.x) // No compiler error
}

The initial value of y does not depend on self and does not need a type annotation. The initial values of z1/z2 depend on self, and it compiles only with an explicit type annotation.

Update: This has been fixed in Swift 4/Xcode 9 beta 3, lazy property initializers can now reference instance members without explicit self, and without explicit type annotation. (Thanks to @hamish for the update.)

Piper answered 30/6, 2016 at 9:23 Comment(7)
W.r.t. "some reason": self may be of type SecondClass or a subclass type of SecondClass. This means the return type of some method of self may differ at runtime depending on what self is in the class hierarchy. If we were to use the return value of such a class method to lazily instantiate first, then we couldn't know at compile time the type of first (due to overriden return type in subclass), hence the requirement for explicit type annotation of first. Now, this shouldn't be an issue when using self as an argument to an initializer, but possibly the case above generalizes.Emlen
(where by "return type of some method self may differ at runtime" I refer to the case where the subclass introduces a new same-name method with same method parameters but different return type; yielding an ambiguous method choice in case self is a subclass object and if the lazy variable were not to have been explicitly type annotated. It's weird, though, that the compiler can't infer type e.g. from a supplied explicit return type when using a closure to instantiate the lazy var, e.g. lazy var first = { () -> FirstClass in FirstClass(second: self) }()).Emlen
@dfri: Thanks for your response. However, the problem seems to be unrelated to subclassing, I have added an example.Piper
Also mentioned here: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…, but I haven't found the rationale for it yet.Piper
I see, the mystery remains unsolved. Meanwhile, categorized as type inferrence weirdness, I guess.Emlen
(W.r.t. swift-evo thread: interesting that apple dev Joe Groff is the one who mentions that, as far as he knows, lazy vars cannot refer to self (so maybe the type annotation is a semi-heck of a not fully complete feature))Emlen
Thankfully, this quirk is fixed in Swift 4 (specifically the build that ships with Xcode 9 beta 3) :)Zosima

© 2022 - 2024 — McMap. All rights reserved.