Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to restrict a Swift generic class function return type to the same class or subclass?

I am extending a base class (one which I do not control) in Swift. I want to provide a class function for creating an instance typed to a subclass. A generic function is required. However, an implementation like the one below does not return the expected subclass type.

class Calculator {
    func showKind() { println("regular") }
}

class ScientificCalculator: Calculator {
    let model: String = "HP-15C"
    override func showKind() { println("scientific") }
}

extension Calculator {
    class func create<T:Calculator>() -> T {
        let instance = T()
        return instance
    }
}

let sci: ScientificCalculator = ScientificCalculator.create()
sci.showKind()

The debugger reports T as ScientificCalculator, but sci is Calculator and calling sci.showKind() returns "regular".

Is there a way to achieve the desired result using generics, or is it a bug?

like image 507
sean woodward Avatar asked Dec 06 '14 21:12

sean woodward


2 Answers

Ok, from the developer forums, if you have control of the base class, you might be able to implement the following work around.

class Calculator {
    func showKind() { println("regular") }
    required init() {}
}

class ScientificCalculator: Calculator {
    let model: String = "HP-15C"
    override func showKind() { println("\(model) - Scientific") }
    required init() {
        super.init()
    }
}

extension Calculator {
    class func create<T:Calculator>() -> T {
        let klass: T.Type = T.self
        return klass()
    }
}

let sci:ScientificCalculator = ScientificCalculator.create()
sci.showKind()

Unfortunately if you do not have control of the base class, this approach is not possible.

like image 57
sean woodward Avatar answered Sep 29 '22 07:09

sean woodward


There is a bug somewhere - I mean, not in your code :)

This is obviously a generic method:

class func create<T:Calculator>() -> T

T is inferred by the type of the variable the return value is assigned to, which in your case is ScientificCalculator.

A more interesting case. Let's modify the create function such that the type of T is made explicit, regardless of the type of the variable the instance is assigned to:

class func create<T:Calculator>(type: T.Type) -> T

let sci: ScientificCalculator = ScientificCalculator.create(ScientificCalculator.self)

The result is the same.

A more interesting observation: sci is a variable of ScientificCalculator type, but it points to an instance of Calculator. So this code:

sci.model

compiles, but generates a runtime exception - because there's no model property defined in the base class.

This is clearly a compiler bug: an instance of a superclass is assigned to a variable whose type is one of its subclasses - that should never be possible (although the opposite is possible)

Also read my answer to a similar question

like image 25
Antonio Avatar answered Sep 29 '22 07:09

Antonio