4

here is my code

class Foo<T> {

}

class Main {
    static func test() {
        var foo: Foo<Any>

        var bar = Foo<String>()
        //Do all my stuff with foo necessitating String

        foo = bar
    }
}

When I try to assign foo = bar I get an error Cannot assign a value of type Foo<String> to a value of type Foo<Any>. I don't understand why I got this error, since String conforms to Any. If I do exactly the same thing but using Array, I don't have any error

static func test() {
        var foo: Array<Any>

        var bar = Array<String>()
        //Do all my stuff with foo necessitating String

        foo = bar
    }

Does anyone know what's wrong with my code? Thanks

jtbandes
  • 115,675
  • 35
  • 233
  • 266
Jaeger
  • 324
  • 1
  • 10

2 Answers2

2

Arrays and dictionaries are special types that have this kind of behavior built in. This does, however, not apply to custom generic types. The type Foo<Any> is not a supertype (or in this case superclass) of Foo<String> even though Any is a supertype of String. Therefore, you cannot assign variables of these types to each other.

Depending on your particular case the solution outlined in Swift Cast Generics Type might work for you. When Foo wraps a value of type T you can add a generic constructor that converts the value from a differently typed instance of Foo.

class Foo<T> {
    var val: T

    init(val: T) {
        self.val = val
    }

    init<O>(other: Foo<O>) {
        self.val = other.val as! T
    }
}

class Main {
    static func test() {
        var foo: Foo<Any>

        var bar = Foo<String>(val: "abc")
        //Do all my stuff with foo necessitating String

        foo = Foo<Any>(other: bar)
    }
}
Community
  • 1
  • 1
hennes
  • 9,147
  • 4
  • 43
  • 63
  • How is the "specialness" of array and dict declared? Thanks. – Darko Aug 10 '15 at 19:11
  • How come foo.val.dynamicType prints Swift.String instead of Swift.Any? – Will M. Aug 10 '15 at 19:26
  • @Darko Sorry, I don't know that. It's Swift internals. I think the main difference is that arrays and dictionaries are not classes but structs and probably receive special treatment by the compiler. – hennes Aug 10 '15 at 19:38
  • @WillM.That's an interesting catch. Actually, `var x: Any = "abc"; print(x.dynamicType)` prints out `Swift.String` whereas `var x: AnyObject = "abc"; print(x.dynamicType)` prints `Swift._NSContiguousString`. I guess the `Any` type just serves as a blind proxy to the underlying actual type. – hennes Aug 10 '15 at 19:44
  • @hennes I made some improvements to your answer to make it more powerful and a bit safer – Will M. Aug 10 '15 at 20:21
  • @WillM. That's a nice enhancement! :) – hennes Aug 11 '15 at 06:11
2

I have improved on @hennes answer by adding the ability to define how you want to convert from type O to type T

class Foo<T> {
    var val: T

    init(val: T) {
        self.val = val
    }

    init<O>(other:Foo<O>, conversion:(O) -> T) {
        self.val = conversion(other.val)
    }
}

class Main {
    static func test() {
        var foo: Foo<Int>

        var bar = Foo<String>(val: "10")
        //Do all my stuff with foo necessitating String

        foo = Foo<Int>(other: bar, conversion: {(val) in
            return Int(val.toInt()!)
        })
        print(foo.val) //prints 10
        print(foo.val.dynamicType) //prints Swift.Int
    }
}

This gives you the ability to convert between two types that don't support casting to each other. Additionally, it gives a compiler warning when the conversion is illegal, as opposed to crashing due to the forced cast.

No idea why there is a difference in compiler warnings for 1 or >1 line closures but there are.

Bad compiler warning for 1-line closure useless compiler warning

Good compiler warning for 2-line closure enter image description here

Will M.
  • 1,864
  • 17
  • 28