What is a use-site projection in Kotlin, and why do you need Array<out T> when copying from a source array even though Array is invariant?
answer
- Array is invariant by default
- out = producer/read, in = consumer/write
- copy(from: Array<out Any>, to: Array<Any>)
- out forbids set, in narrows get to Any?
- projection = variance at the use site
basics
~10 sArray<T> is invariant, so an Array<Int> is not an Array<Any>. Writing Array<out T> at a parameter says 'I only read from this array', which lets you pass arrays of subtypes safely.
solid answer
~40 sKotlin's Array<T> is invariant: Array<Int> is NOT a subtype of Array<Any>, because you could write a String into an array you read as Int. A use-site projection adds variance only at the place you use the type. Array<out Any> means the function only treats it as a producer of Any, so you may pass Array<Int>, Array<String>, etc. In return for that flexibility the compiler forbids calling members that consume T (like set), since the real element type is unknown. The classic example is fun copy(from: Array<out Any>, to: Array<Any>): you read from from and write to to. Without out on from, you could not pass Array<Int>. This is sometimes called type projection; it mirrors Java's Array<? extends Object> wildcard but is expressed with the out keyword.
code
kotlin · 7 linesfun fill(dest: Array<in Int>, value: Int) {
for (i in dest.indices) dest[i] = value // set is allowed (in)
}
val objs: Array<Any> = arrayOf<Any>(0, 0, 0)
fill(objs, 7) // Array<Any> accepted because dest is Array<in Int>
// dest[0] would be typed Any?, so reads are limitedgo deeper
Knows Array is invariant and that Array<out T> lets you pass subtype arrays for reading.
Explains the safety reason (set would be unsafe) and that out forbids set.
Connects use-site projection to declaration-site variance and the PECS-style intuition without conflating them.
Frames projection as the per-use opt-in that keeps an invariant mutable type usable while preserving soundness.
## The problem: Array is invariant In Kotlin, generic classes are **invariant** by default. For `Array<T>` this means `Array<Int>` is **not** a subtype of `Array<Any>`, and vice versa. That is deliberately safe: `Array` lets you both read (`get`) and write (`set`) elements. If `Array<Int>` were an `Array<Any>`, you could do `(arr as Array<Any>)[0] = "oops"` and corrupt an int array. ## What a use-site projection is A **use-site projection** applies variance at the **place the type is used** (a parameter, property, or variable type) rather than on the class declaration. You write `out` or `in` in front of the type argument: - `Array<out T>` — a **producer**: you may only call members that *return* `T` (read `get`), not members that *consume* `T` (write `set`). - `Array<in T>` — a **consumer**: you may only call members that *accept* `T` (write `set`), not members that *return* `T` as `T` (`get` returns `Any?`). This is also called **type projection**: the projected type exposes only the safe half of the API. ## The canonical copy example ```kotlin fun copy(from: Array<out Any>, to: Array<Any>) { assert(from.size == to.size) for (i in from.indices) { to[i] = from[i] // read from `from`, write into `to` } } val ints: Array<Int> = arrayOf(1, 2, 3) val any: Array<Any> = arrayOfNulls<Any>(3) as Array<Any> copy(ints, any) // OK because `from` is Array<out Any> ``` Without `out` on `from`, the call `copy(ints, any)` would not compile, because `Array<Int>` is not an `Array<Any>`. The `out` projection says "`from` is only ever read", which makes accepting `Array<Int>` safe. ## Why it is safe Inside `copy`, `from[i]` (a `get`) is typed as `Any`, which is fine. The compiler **forbids** `from[i] = x` (a `set`), because the actual element type might be `Int` and writing an arbitrary `Any` would be unsafe. So the projection trades away the write half of the API for the freedom to pass subtype arrays. ## Key keywords - `out` at a use site = covariant projection = read-only-ish view. - `in` at a use site = contravariant projection = write-only-ish view. - Invariant by default — projection is opt-in per use.
- Why doesn't Kotlin just make Array covariant like List?Because Array is mutable: it has set(i, T), which consumes T. A class can only be declared covariant if T never appears in an 'in' position. read-only List<out E> can; Array cannot, so you project per use instead.
Array<out T> is like a one-way 'EXIT' door: things can come out, but the compiler bolts the 'enter' (set) side shut so nothing wrong gets pushed in.
saying these in an interview costs you the question
- Saying Array<Int> is a subtype of Array<Any> in Kotlin
- Confusing use-site projection with casting
- Claiming out lets you write to the array
- Thinking projection changes the class declaration
- Believing projection is just syntactic sugar with no compile-time effect