In Kotlin, can a function return either String or Int?

I’m a beginner to Kotlin, most familiar with Python.

Coming from a dynamically typed language, I became curious with this.
In Kotlin, can a function return either one of multiple types?
For example, can a function return either Int or String type?
+ assuming same function signature (i.e., function name and parameter types), so that we can’t do function overloading

It seems this is also not possible in Java.
Is there something like Union[Int, String]? (which is type hinting from Python 3),
or is there any workaround to it?

1 Like

See this thread for the discussion about union types: Union types

1 Like

I’m interested in this as well and browsed through the thread … and I didn’t get what the conclusion of the discussion is and how one would emulate union types in Kotlin. Any pointers?

The following works but is not typesafe:

fun test(n: Int): Any = if (n >= 0) n else "$n is negative"

How to implement test in a typesafe way?

Thanks for the reply. So the Union types are currently not supported. I currently can’t really come up with a use case why I would need such kind of function, it was just purely theoretical. But is there actually a workaround to emulate this behavior, other than union types?

I also thought of defining a function with return type Any and saw that it works, but I guess this would be considered as a terrible design.

You can use sealed classes, please check official documentation here.

For example, you can write something like this:

sealed class TestResult {
     class Int(val value: Int): TestResult()

     class String(val value: String): TestResult()
}

fun test(n: Int): TestResult = if (n >= 0) TestResult.Int(n) else TestResult.String("$n is negative")

Next you can use when clause to check all possible types in compile type.

5 Likes

I think this depends on which language you are used to working with. In JavaScript this is quite normal: for many functions you can pass different types to parameters, and the function figures out what to do based on the received type.

I think it is fine if you split the testing of the type and the actual processing for each type into different functions:

fun handle(x: Any) {
    when (x) {
        is String -> handleString(x)
        is Int -> handleInt(x)
        ...
    }
}
2 Likes

Thanks a lot for the instructive example!

A minor correction, because String and Int refer to the inner classes but are supposed to be kotlin.Int and kotlin.String:

sealed class TestResult {
    class Int(val value: kotlin.Int): TestResult()
    class String(val value: kotlin.String): TestResult()
}

fun test3(n: Int): TestResult =
    if (n >= 0) TestResult.Int(n) else TestResult.String("$n is negative")
1 Like

True, but you probably want to come up with better names than Int or String for your use case.

2 Likes

It is worse, many JavaScript libraries will return something different based upon the value passed in the parameter. An example would be JQuery’s jquery (or $) function that overloads (almost) everything through a single function and then tries to guess what the (generally string) parameter means (is it a tag, is it a selector, is it …?).

Nope. But you could use the Either type available in arrow.

You might be familiar with using the Pair() class to define an int and string as a pair. See official documentation with examples here:

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-pair/index.html

You can use sealed classes, please check official documentation here.

I would say that this is a best solution for the problem described in the original message.
Though in official documentation example show it without nesting (which was mandatory prior version 1.1). So with the newer versions the example would look like this.

sealed class TestResult
class TestResultInt(val value: Int): TestResult()
class TestResultString(val value: String): TestResult()

fun test(n: Int): TestResult = if (n >= 0) TestResultInt(n) else TestResultString("$n is negative")

I just don’t really see a real usage for such case. If the initial idea was to return either the result or an error, then I would say some other solution will be better. Usually Kotlin follows Java way to handle error situations: throws an exception
This will return not negative n or throw IllegalStateException with provided message.

fun test(n: Int): Int {
  check(n >=0) { "$n is negative" }
  return n
}

This will return not negative n or throw IllegalArgumentException with provided message.

fun test(n: Int): Int {
  require(n >=0) { "$n is negative" }
  return n
}

If custom exception should be thrown, then this will do

fun test(n: Int): Int = if (n >= 0) n else throw MyException("$n is negative") 

In some languages instead of using exception to return tuple from the function with result or error. In Kotlin this can be implemented using Pair where for example first element would be expected result and second is possible error or error message. Drawback of this approach is that nullable types should be used.

fun test(n: Int): Pair<Int?, String?> = if (n >= 0) Pair(n, null) else Pair(null, "$n is negative")

val (result, error) = test(someNumber)
if (error != null) {
  // do something in case of error
}
checkNotNull(result) // I prefer to use Kotlin's smart cast but it is possible just to use !!
1 Like