How Covariance and Contravariance Work in Kotlin’s Function Types

Table of Contents

Kotlin’s function type variance is an important concept that helps developers understand how types behave when dealing with function inputs and outputs. If you’ve ever wondered why function parameters and return types follow different variance rules, this post will clear things up..!

Understanding Variance in Kotlin

Variance determines how subtyping relationships work in a type hierarchy. There are two key types of variance in Kotlin:

  1. Covariance (Out-projected types): A subtype can replace a supertype when reading values.
  2. Contravariance (In-projected types): A supertype can replace a subtype when writing values.

Function Types and Variance

Kotlin functions can be represented as types, for example:

Kotlin
val func: (String) -> Int = { it.length }

Here, func is a function type that takes a String as input and returns an Int. Function types in Kotlin have two parts:

  1. Parameter type(s) — What the function takes in.
  2. Return type — What the function returns.

The variance in function types applies differently to parameters and return types.

  • Return types are covariant (out).
  • Parameter types are contravariant (in).

Now, let’s explore how these apply to Kotlin’s function type variance.

Covariance and Contravariance in Kotlin’s Function Types

In Kotlin, a class or interface can be covariant on one type parameter and contravariant on another. One of the classic examples of this is the Function interface. Let’s take a look at the declaration of the Function1 interface, which represents a one-parameter function:

Kotlin
interface Function1<in P, out R> {
    operator fun invoke(p: P): R
}

To make the notation more readable, Kotlin provides an alternative syntax (P) -> R to represent Function1<P, R>. In this syntax, you’ll notice that P (the parameter type) is used only in the in position and is marked with the in keyword, while R (the return type) is used only in the out position and is marked with the out keyword.

This means that the subtyping relationship for the function type is reversed for the first type argument (P) and preserved for the second type argument (R).

For example, let’s say you have a higher-order function called enumerateCats that accepts a lambda function taking a Cat parameter and returning a Number:

Kotlin
fun enumerateCats(f: (Cat) -> Number) { ... }

Now, suppose you have a function called getIndex defined in the Animal class that returns an Int. You can pass Animal::getIndex as an argument to enumerateCats:

Kotlin
fun Animal.getIndex(): Int = ...
enumerateCats(Animal::getIndex)       // This code is legal in Kotlin. Animal is a supertype of Cat, and Int is a subtype of Number

In this case, the Animal::getIndex function is accepted because Animal is a supertype of Cat, and Int is a subtype of Number, the function type’s subtyping relationship allows it.

The function (T) -> R is contravariant on its argument and covariant on its return type

This illustration demonstrates how subtyping works for function types. The arrows indicate the subtyping relationship.

Function Type Variance in Kotlin’s Type System

As you now know, when working with function types in Kotlin, variance is explicitly defined:

Kotlin
interface Function1<in P, out R> {
    fun invoke(param: P): R
}
  • P (parameter) is contravariant (in)
  • R (return type) is covariant (out)

This aligns with the natural logic of function type variance.

Conclusion

So in summary,

  • Covariance (out) applies to return types – A function returning a subtype can be assigned to a function returning a supertype.
  • Contravariance (in) applies to parameter types – A function taking a supertype can be assigned to a function taking a subtype.
  • Kotlin enforces these rules to ensure type safety and prevent runtime errors.

Understanding Kotlin’s Function Type Variance helps write flexible, reusable, and type-safe code, especially when designing APIs, lambda functions, and higher-order functions.

Skill Up: Software & AI Updates!

Receive our latest insights and updates directly to your inbox

Related Posts

error: Content is protected !!