What's Behind the Word out in Kotlin? Answering Popular Questions
\n\nIf you've ever written generics in Kotlin and encountered a confusing compilation error, you've likely come across the word out. This small modifier sparks a lot of debate even among experienced developers. We've gathered the most common questions from Stack Overflow and turned them into an easy-to-understand guide. Today, you'll learn what out is, why it's needed, and how not to get confused by covariance.
Let's figure it out once and for all — no boring theory, just live code and practice.
\n\nWhy Did Kotlin Invent out? (A Brief History)
\n\nIn Java, there's a wildcard — the question mark ? extends T. It means a collection can contain objects of type T or its subtypes. The problem is that Java doesn't prevent you from accidentally putting the wrong object into such a collection — you'll only get an error at runtime. Kotlin solved this problem at the type level.
The keyword out in Kotlin is a declaration of covariance. If you write Source<out T>, it means: "I can read objects of type T from this Source, but I cannot write to it." The compiler strictly enforces this.
Question 1: How is out Different from in?
\n\nThe most common question on Stack Overflow. Let's imagine a box:
\n\n- \n
out— a box from which you can only take things out (produces). \n in— a box into which you can only put things (consumes). \n
Example:
\n\n// Covariance: can read, but cannot write\ninterface Producer<out T> {\n fun produce(): T\n}\n\n// Contravariance: can write, but cannot read\ninterface Consumer<in T> {\n fun consume(item: T)\n}\n\nfun main() {\n val stringProducer: Producer<String> = object : Producer<String> {\n override fun produce(): String = "Hello"\n }\n \n val anyProducer: Producer<Any> = stringProducer // Works! out allows this\n println(anyProducer.produce())\n}\n\n\nNote: Producer<out T> allows assigning Producer<String> to a variable of type Producer<Any>. Without out, this would be impossible.
Question 2: Why Does Kotlin Forbid fun set(value: T) with out?
\n\nThis is a classic trap. If you declare out, the compiler won't allow functions that accept T as a parameter. Why?
Imagine you have a MutableList<out Number>. You could put an Int into it, but the compiler doesn't know what the actual list is. Maybe it's a MutableList<Double>? If writing were allowed, you'd break the types. out guarantees safety: you can only read.
// Won't compile!\ninterface BadList<out T> {\n fun get(index: Int): T // OK\n fun add(item: T) // Error! T is in an in-position\n}\n\n\nSolution: if you need both reading and writing, use in and out separately, or use an invariant type (without a modifier).
\n\nQuestion 3: How is out Related to the Java Wildcard ? extends?
\n\nDirect correspondence: Source<out T> in Kotlin = Source<? extends T> in Java. But there's an important difference:
- \n
- In Java, wildcards are use-site variance. \n
- In Kotlin,
outis declaration-site variance. \n
Example in Java:
\n\n// Java: using wildcard when declaring a variable\nList<? extends Number> numbers = new ArrayList<Integer>();\nnumbers.a