ceving
ceving

Reputation: 23814

Why "out T" for constructor input?

I found the following code in the Kotlin forum and it works fine.

sealed class JsonValue<out T>(val value: T) {
    class JsonString(value: String) : JsonValue<String>(value)
    class JsonBoolean(value: Boolean) : JsonValue<Boolean>(value)
    class JsonNumber(value: Number) : JsonValue<Number>(value)
    object JsonNull : JsonValue<Nothing?>(null)
    class JsonArray<V>(value: Array<V>) : JsonValue<Array<V>>(value)
    class JsonObject(value: Map<String, Any?>) : JsonValue<Map<String, Any?>>(value)

    override fun toString(): String = value.toString()
}

fun main() {
    var pi: JsonValue<Any?>
    pi = JsonValue.JsonString("pi"); println (pi)
    pi = JsonValue.JsonNumber(3.14); println (pi)
    pi = JsonValue.JsonNull; println (pi)
}

But I do not understand why it uses out T.

An answer to a question about out in general states:

out T [...] means functions can return T but they can't take T as arguments.

in T [...] means functions can take T as arguments but they can't return T.

If I take a look at the above code, I can see many constructors (functions), which take T (the value) as an argument. And I see no function which returns T. So my inital impression was: this must be a typo, it should be in T. But it does not even compile with in T.

Why is it necessary to use out T, although the type goes into the constructor?

Upvotes: 1

Views: 75

Answers (1)

Sweeper
Sweeper

Reputation: 271070

The constructor doesn't really count :) Only instance members matter - things that you can do to instances of JsonValue.

As explained in the linked answer, the whole idea of (declaration-site) covariance is that you are allowed to implicitly convert an instance of e.g. JsonValue<String> to JsonValue<Any?> if the type JsonValue<T> satisfies some requirements. One of the requirements is that JsonValue<T> should not have any functions that take in any Ts*, because if it did, weird things like this would happen:

val x: JsonValue<Any?> = JsonString("foo")
x.giveMeSomeT(123)

x at runtime holds an instance of JsonString, but the giveMeSomeT method in JsonString would expect a String, not an Int, but as far as the compiler is concerned, x is a JsonValue<Any?>, so this should compile, and bad things would happen at runtime.

So this is why having a function that takes in Ts stops you from marking JsonValue as out T. However, having a constructor that takes in a T is not problematic at all, since situations like the above cannot happen with just a constructor.

And I see no function which returns T

In fact, the getter of value returns T. Also note that you do not need something that returns T to in order to say out T. You just need to to have nothing that takes in Ts. This is vacuously valid for example:

class Foo<out T>

* More accurately and generally, whenever I say "take in any Ts", it should be "have T in an 'in' position", and whenever I say "return a T", it should be "have T in an 'out' position". This is to account for Ts being used as the type argument of other generic types.

Upvotes: 3

Related Questions