Last time, I set out to build a Result<T, E> type in Kotlin, and discovered some awkwardness around parameterizing the types of the variants. This issue, I realize I totally forgot about covariance, which significantly mitigates the awkwardness.

Covariance and its converse, contravariance, are terms for the ways sub- and supertypes can be substituted for one another. Kotlin makes the choice that parameterized types, unless otherwise specified, are invariant—that is, if you have Result<T, E>, then Result<Int, Int> is not a subtype of Result<Any, Any> (nor vise versa). This is what makes us overparameterize the types of our variants. We can't specfy an E for Ok other than, well, E.

However, Kotlin does allow you to otherwise specify, with the in and out modifiers for type parameters. (As an aside, while Kotlin chose those particular keywords to try to be clearer than the CS jargon or Java syntax, I actually find them very confusing. The breakdown, for reference, is:

  • Covariance: subtypes of T may be substituted for T (e.g. Int for Any). This is indicated in Kotlin with <out T>, and in Java with <? extends T>.
  • Contravariance: supertypes of T may be substituted for T (e.g. Any for Int). This is indicated in Kotlin with <in T> and in Java with <? super T>.)
In our case, we want our Result to be covariant in both parameters, so we can use a Result<Int, Int> as a Result<Any, Any>. So our updated declaration looks like sealed class Result<out T, out E>() .

There's one more thing we need before we start to assemble this. We still need to parameterize Result for each of our variants; and so we'll need some type which is a subtype of any possible T or E. This is called a bottom type; it's a type for which no instances can exist, which means we can truthfully make silly claims about those instances, like "it can be substituted for T". Fortunately, Kotlin does provide a bottom type, called Nothing.

If we use Nothing for the unused parameter of our Result variants, we get: sealed class Result<out T, out E>() { /** Contains the success value */ data class Ok<T>(val ok: T): Result<T, Nothing>() /** Contains the error value */ data class Err<E>(val err: E): Result<Nothing, E>() } This compiles, and more importantly, works correctly: fun main() { val foo: List<Result<Int, Int>> = listOf(Result.Ok(1), Result.Err(2)) println(foo) } [Ok(ok=1), Err(err=2)] That cleaned up nicely!