Type Coercion Issue in Swift

Swift generic coercion misunderstanding

The difference is that Array (and Set and Dictionary) get special treatment from the compiler, allowing for covariance (I go into this in slightly more detail in this Q&A).

However arbitrary generic types are invariant, meaning that X<T> is a completely unrelated type to X<U> if T != U – any other typing relation between T and U (such as subtyping) is irrelevant. Applied to your case, Signal<ChildClass> and Signal<BaseProtocol> are unrelated types, even though ChildClass is a subtype of BaseProtocol (see also this Q&A).

One reason for this is it would completely break generic reference types that define contravariant things (such as function parameters and property setters) with respect to T.

For example, if you had implemented Signal as:

class Signal<T> {

var t: T

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

If you were able to say:

let signalInt = Signal(t: 5)
let signalAny: Signal<Any> = signalInt

you could then say:

signalAny.t = "wassup" // assigning a String to a Signal<Int>'s `t` property.

which is completely wrong, as you cannot assign a String to an Int property.

The reason why this kind of thing is safe for Array is that it's a value type – thus when you do:

let intArray = [2, 3, 4]

var anyArray : [Any] = intArray
anyArray.append("wassup")

there are no problems, as anyArray is a copy of intArray – thus the contravariance of append(_:) is not a problem.

However, this cannot be applied to arbitrary generic value types, as value types can contain any number of generic reference types, which leads us back down the dangerous road of allowing an illegal operation for generic reference types that define contravariant things.


As Rob says in his answer, the solution for reference types, if you need to maintain a reference to the same underlying instance, is to use a type-eraser.

If we consider the example:

protocol BaseProtocol {}
class ChildClass: BaseProtocol {}
class AnotherChild : BaseProtocol {}

class Signal<T> {
var t: T

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

let childSignal = Signal(t: ChildClass())
let anotherSignal = Signal(t: AnotherChild())

A type-eraser that wraps any Signal<T> instance where T conforms to BaseProtocol could look like this:

struct AnyBaseProtocolSignal {
private let _t: () -> BaseProtocol

var t: BaseProtocol { return _t() }

init<T : BaseProtocol>(_ base: Signal<T>) {
_t = { base.t }
}
}

// ...

let signals = [AnyBaseProtocolSignal(childSignal), AnyBaseProtocolSignal(anotherSignal)]

This now lets us talk in terms of heterogenous types of Signal where the T is some type that conforms to BaseProtocol.

However one problem with this wrapper is that we're restricted to talking in terms of BaseProtocol. What if we had AnotherProtocol and wanted a type-eraser for Signal instances where T conforms to AnotherProtocol?

One solution to this is to pass a transform function to the type-eraser, allowing us to perform an arbitrary upcast.

struct AnySignal<T> {
private let _t: () -> T

var t: T { return _t() }

init<U>(_ base: Signal<U>, transform: @escaping (U) -> T) {
_t = { transform(base.t) }
}
}

Now we can talk in terms of heterogenous types of Signal where T is some type that's convertible to some U, which is specified at the creation of the type-eraser.

let signals: [AnySignal<BaseProtocol>] = [
AnySignal(childSignal, transform: { $0 }),
AnySignal(anotherSignal, transform: { $0 })
// or AnySignal(childSignal, transform: { $0 as BaseProtocol })
// to be explicit.
]

However, the passing of the same transform function to each initialiser is a little unwieldy.

In Swift 3.1 (available with Xcode 8.3 beta), you can lift this burden from the caller by defining your own initialiser specifically for BaseProtocol in an extension:

extension AnySignal where T == BaseProtocol {

init<U : BaseProtocol>(_ base: Signal<U>) {
self.init(base, transform: { $0 })
}
}

(and repeat for any other protocol types you want to convert to)

Now you can just say:

let signals: [AnySignal<BaseProtocol>] = [
AnySignal(childSignal),
AnySignal(anotherSignal)
]

(You can actually remove the explicit type annotation for the array here, and the compiler will infer it to be [AnySignal<BaseProtocol>] – but if you're going to allow for more convenience initialisers, I would keep it explicit)


The solution for value types, or reference types where you want to specifically create a new instance, to is perform a conversion from Signal<T> (where T conforms to BaseProtocol) to Signal<BaseProtocol>.

In Swift 3.1, you can do this by defining a (convenience) initialiser in an extension for Signal types where T == BaseProtocol:

extension Signal where T == BaseProtocol {
convenience init<T : BaseProtocol>(other: Signal<T>) {
self.init(t: other.t)
}
}

// ...

let signals: [Signal<BaseProtocol>] = [
Signal(other: childSignal),
Signal(other: anotherSignal)
]

Pre Swift 3.1, this can be achieved with an instance method:

extension Signal where T : BaseProtocol {
func asBaseProtocol() -> Signal<BaseProtocol> {
return Signal<BaseProtocol>(t: t)
}
}

// ...

let signals: [Signal<BaseProtocol>] = [
childSignal.asBaseProtocol(),
anotherSignal.asBaseProtocol()
]

The procedure in both cases would be similar for a struct.

Swift issue: About Type Casting for AnyObject

This is a little confusing because of the bridging that Swift does to Objective-C, where most things are classes and there are fewer value-types. To see what Swift does without the bridge, create a new playground and delete the import ... statement at the top, then try casting an Int to Any (no problem) and AnyObject:

let num = 23                               // 23
let anyNum: Any = num // 23
let anyObjectNum: AnyObject = num
// error: type 'Int' does not conform to protocol 'AnyObject'

Now add import Foundation at the top of your playground:

import Foundation

let num = 23 // 23
let anyNum: Any = num // 23
let anyObjectNum: AnyObject = num // 23

The error goes away - why? When it sees the attempt to cast an Int to AnyObject, Swift first bridges num to be an instance of the Objective-C NSNumber class, found in Foundation, then casts it to the desired AnyObject. The same thing is happening in your array.

You can more or less prove this is the case using the is keyword - since NSNumber bridges to all the numeric types in Swift, it returns true in some funny cases:

anyNum is Int                              // true
anyNum is Double // false
anyObjectNum is Int // true
anyObjectNum is UInt // true
anyObjectNum is Double // true
anyObjectNum is Float // true

Cannot convert value of type 'Character' to type 'String' in coercion

Looking at the documentation, you can see, that Character is just one of the several representations of String in Swift (emphasis added to the relevant parts by me)

A string is a series of characters, such as "hello, world" or
"albatross". Swift strings are represented by the String type. The
contents of a String can be accessed in various ways, including as a
collection of Character values.

In Swift, String is not just an array of Characters unlike in some other languages. In Swift, Character is just a way to represent a String instance in a certain way. Strings can be represented using Views, such as CharacterView, utf8View, etc.

One of the key principles behind the architecture of Swift's String type was Unicode correctness, which is one of the reasons Strings are not just simply an array of Characters.

For more information about the changes to String in Swift4, see the String Manifesto.

To be more specific about why casting doesn't work. There are two kinds of castings, type casting and bridge-casting. Type casting is only possible between classes, where inheritance is involved. You can either upcast a subclass to its superclass, which always succeeds or you can try to downcast a superclass to a subclass, which only works if a subclass instance was first upcasted to its superclass.

It should be quite clear from the above explanation why type casting doesn't work between Character and String, since the neither of the two types inherit from each other.

For bridge casting, this is a method Apple introduced for interoperability between some Swift and Foundation types, such as String and NSString, but since both String and Character are Swift types, bridge casting has nothing to do with this problem either.

How to fix 'Cannot convert value of type '[Any]' to type 'String' in coercion' error in swift

Got the solution:

var arr = data[0]
yourString: String = (arr as AnyObject).description

Swift 4 - Generic Type Conversion issue - Cannot convert to expected type '(_) - Void'

In your f2, the type of closure is @escaping (ValueType, @escaping (ValueType2) -> Void) -> Void.

The type of the first parameter is ValueType.
But you are using it as:

closure(value!, s1.f2)

The type of the first argument value! is Value<ValueType>, not ValueType.


If you change the type of closure to @escaping (Value<ValueType>, @escaping (ValueType2) -> Void) -> Void, your code would compile without any issue.

You may have found some issue about generics, but at least, your code example is not describing the issue.



Related Topics



Leave a reply



Submit