Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Covariant 'Self' type cannot be referenced from a stored property initializer

Tags:

swift

swift5

In the below code, I don't understand why I get the said error when using Self() ? The code just works fine if I replace it with Fireman() .

final class Fireman {

    var numOfServices = 0

    private init(){}
    static var shared = Self() <-------- Here !!!

    func extinguishFire() {
        self.numOfServices += 1
        print("Spraying water...\(self.numOfServices)")
    }
}

Also the reason I had to mark the class final is because, without that the error message was that I had to include a required initializer (and when I do that I again get an error because my initializer is private). So just to avoid further subclassing , though against my will I declared the class final

like image 396
Tilak Maddy Avatar asked Mar 11 '20 20:03

Tilak Maddy


1 Answers

This is pretty similar to Protocol func returning Self, but enough different that it's probably worth answering separately. There are two issues here.

The first issue is why you need final. As in the above question, the problem issue is that you're making a promise the compiler can't prove you'll keep.

Consider the following subclass:

class LocalFireman: Fireman {
    let zipcode: String
    init(zipcode: String) { self.zipcode = zipcode }
}

What should LocalFireman.shared return? It can't return a Fireman. You said it had to return Self (i.e. LocalFireman). And it can't return a LocalFireman, since it doesn't have a zipcode to init with. So now what?

Swift doesn't let you get into this corner by requiring you to either nail down the specific type of shared (i.e. it will always be Fireman, even if you call it on a subclass), or you need to promise there will be no subclasses, or you need to require all subclasses to implement init:

required init() {}

OK, but you got that far. You marked it final, and it still complains. Now you're hitting a compiler limitation. The whole point of Self is covariance; it's the dynamic type at the point of calling, not the static type in context. It's not just a handy way to say "my type" and this was an choice (though it's something that could change I think). It means "my covariant type" even if you're in a situation where there can't be any subtypes.

All that said, given the private init, I'm confused why you say marking it final is "against my will." If all init are private, this can't be subclassed anyway, so it seems you want a final class, and if so, then just put the class name where it goes. Self is not for that problem (today).

This leaves open the question as to why you can't do this with a required init. Self as a "type of class" and Self as "type of thing conforming to a protocol" are treated as the same thing. So you can't think about this only in the class situation; anything can "inherit" (conform to) a protocol. So Self can potentially refer to a struct. Structs can be any size. So allowing a stored property to be of type Self creates memory layout problems. (I don't think Swift promises that classes will always be implemented as a pointer, either, so this could cause the same problem for classes, at least in principle.)

You can return a value of type Self from a function, but you can't put one in a stored property (this is true both of static and non-static properties), and Swift also doesn't allow them for computed properties (I assume this is just for consistency). So the following is allowed (and in fact useful):

class Fireman {
    required init() {}
    static func make() -> Self { return Self() }
}

And this is what Self is for.

like image 95
Rob Napier Avatar answered Nov 14 '22 00:11

Rob Napier