Swift generics not preserving type

2019-01-06 18:08发布

问题:

I'm trying cast and/or generate a variable based upon the specified generic type. I understand there is no type erasure in swift, but it doesn't seem like the generics preserve type other than the specified conditions of the generic e.g. conforming to a base class. It seems like all i can cast or initialize is the base class. What's even more strange is when i'm in the debugger the generic appears to have a RawPointer to the correct class and even the variables look like they're of the right type:

EDIT:

As of Xcode 6.1 this is still an issue (simplified code courtesy of Gregory Higley) :

class BaseClass {
    func printme() -> Void {
        println("I am BaseClass")
    }
}

class DerivedClass : BaseClass {
    override func printme() -> Void {
        println("I am DerivedClass")
    }
}

class Util<T: BaseClass> {
    func doSomething() {
        var instance = T()
        instance.printme()
    }
}

var util = Util<DerivedClass>()
util.doSomething()

Still prints out "I am BaseClass"

Also would like to note that required init{} in the base class no longer works.

回答1:

This code works as expected.

class BaseClass {

    required init() {} // <-- ADDED THIS

    func printme() -> Void {
        println("I am BaseClass")
    }
}

class DerivedClass : BaseClass {
    override func printme() -> Void {
        println("I am DerivedClass")
    }
}

class Util<T: BaseClass> {
    func doSomething() {
        var instance = T()
        instance.printme()
    }
}

var util = Util<DerivedClass>()
util.doSomething()

Code base are stolen from @GregoryHigley answer :)

Marking init() {} as required did the thing. This guarantees init() is the designated initializer of ANY derived class from BaseClass.

Without it, one can make illegal subclass like:

class IllegalDerivedClass : BaseClass {
    var name:String

    init(name:String) {
        self.name = name
        super.init()
    }

    override func printme() -> Void {
        println("I am DerivedClass")
    }
}

var util = Util<IllegalDerivedClass>()
util.doSomething()

You know this doesn't work because IllegalDerivedClass doesn't inherit init() initializer.

I think, that is the reason of your problem.

Anyway, whose fault is that?

  • Compiler should warn about ambiguousness.
  • Runtime should try to initialize DerivedClass() as specified with T.
  • Debugger should show instance is a instance of BaseClass as it actually is.

ADDED:

As of Xcode 6.1 GM 2, It seems, you need more work. (in addition to required init() {})

class Util<T: BaseClass> {
    let theClass = T.self // store type itself to variable

    func doSomething() {
        var instance = theClass() // then initialize
        instance.printme()
    }
}

I have absolutely no idea why we need this, what's going on X(

ADDED:2014/10/18

I found this also works:

    func doSomething() {
        var instance = (T.self as T.Type)()
        instance.printme()
    }

ADDED: 2015/02/10

As of Xcode Version 6.3 (6D520o) / Swift 1.2

We no longer need (T.self as T.Type)() hack. Just T() works as long as T has required init() initializer.

class Util<T: BaseClass> {
    func doSomething() {
        var instance = T()
        instance.printme()
    }
}


回答2:

I created a simplified version of your code as follows:

class BaseClass {
    func printme() -> Void {
        println("I am BaseClass")
    }
}

class DerivedClass : BaseClass {
    override func printme() -> Void {
        println("I am DerivedClass")
    }
}

class Util<T: BaseClass> {
    func doSomething() {
        var instance = T()
        instance.printme()
    }
}

var util = Util<DerivedClass>()
util.doSomething()

This distills the problem to its essence. One would expect util.doSomething() to print "I am DerivedClass", but it prints "I am BaseClass" every time. This has to be a bug, because no rational type system would work in this way.

I think you should file this with Apple as a bug.



回答3:

The problem is var instance = T() Initialisers are not virtual so the instance is always made with BaseClass()*. The following code uses a class function to work around the problem:

class BaseClass {
    func printme() -> String {
        return "I am BaseClass"
    }
    class func makeInstance() -> BaseClass
    {
        return BaseClass()
    }
}

class DerivedClass : BaseClass {
    override class func makeInstance() -> BaseClass
    {
        return DerivedClass()
    }

    override func printme() -> String {
        return "I am DerivedClass"
    }
}

class Util<T: BaseClass> {
    func doSomething() -> String {
        var instance = T.makeInstance()
        return instance.printme()
    }
}

var util = Util<DerivedClass>()
println("\(util.doSomething())")

I changed the implementation of printme() only because the original code didn't print anything in a playground for some reason.

* I think this is still a bug.



回答4:

I had a similar issue. You need to add required initializer and let realType = T.self and replace T() with realType().

class BaseClass {
    required init() {}
    func printme() -> Void {
        println("I am BaseClass")
    }
}

class DerivedClass : BaseClass {
    override func printme() -> Void {
        println("I am DerivedClass")
    }
}

class Util<T: BaseClass> {
    func doSomething() {
        let realType = T.self // that's it
        var instance = realType()
        instance.printme()
    }
}

var util = Util<DerivedClass>()
util.doSomething()