Robot
Robot

Reputation: 70

Kotlin: Generic type functions: Out-projected type ... prohibits the use of

Given a class:

class Data<T>{
    fun get(): T = Something as T
    fun update(item: T) { }
}

How to do something like this?

fun alterData(d: Data<*>){
    d.update(d.get())
}

The function "alterData" won't compile:

Out-projected type 'Data<*>' prohibits the use of 'fun update(item: T)'

Update: The caller of alterData() don't know the type, so this is not possible:

fun <T>alterData(d: Data<T>){
    d.update(d.get())
}

The only way I found to bypass this limitation is by means of writing the method inside the class, but that's breaks abstraction

Upvotes: 0

Views: 124

Answers (1)

fluidsonic
fluidsonic

Reputation: 4676

* basically means that you don't know what the type T is in this case, so it could be anything.

Because you have defined d as Data<*> the call d.get() returns Any?. The compiler cannot know what type of data d contains. Therefor you have to expect anything that's valid for T, including null.

Because you have defined d as Data<*> the call d.update(…) accepts only Nothing, i.e. no value at all. The compiler cannot know what type of data d is allowed to receive. Therefor it cannot accept anything as it may or may not be valid here.

What you want to tell the compiler is that there is a relationship between what comes out of d (out projection) and what goes into d (in projection):

fun <T> alterData(d: Data<T>) {
    d.update(d.get())
}

Now the compiler knows that whatever comes out of d (through .get()) is of the same type as what you put into d (through .update(…)). In both cases that type is referred to as T.

Note that the "type parameter" T doesn't have to have same name as your Data<T> declaration. The following is equally valid to indicate the relationship:

fun <Something> alterData(d: Data<Something>) {
    d.update(d.get())
}

It may be easier to understand if you break the code down further:

fun alterData(d: Data<*>) {
    // `value` is of type `Any?` because we don't know `T` of `Data`
    val value = d.get()

    // `update` rejects `Any?` b/c we don't know `T` and it may not be valid
    d.update(value)
}
fun <Something> alterData(d: Data<Something>) {
    // `value` is of type `Something` b/c we know that `T` of `Data` is `Something`
    val value = d.get()

    // `update` accepts `Something` b/c we know `T` is `Something`
    d.update(value)
}

As in your case neither the caller nor the callee (alterData) know the actual type of T you"ll have to tell the compiler to ignore compile-time type safety by using a cast.

You can do that either in the caller or the callee. Where it makes more sense depends on the actual use case. Usually the caller knows a little more about the context and the possible type of T than the callee, so it makes sense to perform a cast there.

fun foo(d: Data<*>) {
   // ignore all type safety for `T`
   alterData(d as Data<Any?>)
}

The callee alterData immediately passes a value that it receives from one Data instance (d) back to the same instance. In that case it's safe to assume that that can never go wrong, given your definition of class Data. If that's the case then you can safely perform the case in the alterData function:

fun alterData(d: Data<*>){
    // ignore type safety and allow any value to be passed into `d`
    (d as Data<Any?>).update(d.get())
}

Both approaches will raise a compiler warning that you could ignore using @Suppress("UNCHECKED_CAST").

Upvotes: 3

Related Questions