0

Is it possible in Kotlin to write an inline function with reified type which can return different kinds of Arrays? I think about something like this:

inline fun <reified E> getArray(key: String, defValue: Array<E>): Array<E>? {
    return when(defValue) {
        is Array<Int> -> // ...
        is Array<String?> -> // ...
        else // ...
    }
}

And I would like to call it like this:

fun intArray(size: Int): Array<Int> = Array(size) {i -> 0}
fun stringArray(size: Int): Array<String?> = Array(size) {i -> null}

val strings: Array<Int> = getArray(KEY_INTS, intArray(0))
val strings: Array<String> = getArray(KEY_STRINGS, stringArray(0))

But with that I get the error:

Cannot find check for instance of erased type

Cilenco
  • 6,951
  • 17
  • 72
  • 152
  • If you have limited amount of handled types you should consider splitting this method into multiples with different return types. Inlining huge switch statements generates a lot of garbage byte code. – Pawel Sep 26 '18 at 21:43
  • Since the array element type *is* E, you can check the type E::class rather than the type of defValue – Erwin Bolwidt Sep 26 '18 at 22:06
  • @ErwinBolwidt You mean something like E::class == Int::class or how should I do it? Can you explain this in more detail? Sorry I'm new to Kotlin and still thinking in limitations of Java... – Cilenco Sep 26 '18 at 22:21

1 Answers1

4

Explicitly answering question - You can use it by checking the E class:

inline fun <reified E: Any> getArrayInline(key: String, defValue: Array<E>): Array<E>? {
    return when(E::class) {
        Int::class -> arrayOf(1, 2, 3)
        String::class -> arrayOf("a", "b", "c")
        else -> throw IllegalArgumentException("Invalid class: ${E::class.qualifiedName}")
    } as Array<E>
}

But I discourage using it since:

  1. It's not type safe - you have to perform unsafe cast on the result and it can be called for any array type even if it's not included in the when cases
  2. it's inline - so this entire block of code is copied into bytecode whenever you use the method (see below)
  3. type checking is done at runtime, so it hurts performance

What happens when you use it? Let's check this example:

fun testArrayInline(){
    val test = getArrayInline("key", emptyArray<Int>())
    val test2 = getArrayInline("key2", emptyArray<String>())
}

Simple right? But once you look into generated bytecode it's not so good. For readablity this is Kotlin bytecode decompiled back into Java:

public static final void testArrayInline() {
  String var1 = "key";
  Object[] defValue$iv = new Integer[0];
  KClass var3 = Reflection.getOrCreateKotlinClass(Integer.class);
  Object var10000;
  if (Intrinsics.areEqual(var3, Reflection.getOrCreateKotlinClass(Integer.TYPE))) {
     var10000 = new Integer[]{1, 2, 3};
  } else {
     if (!Intrinsics.areEqual(var3, Reflection.getOrCreateKotlinClass(String.class))) {
        throw (Throwable)(new IllegalArgumentException("Invalid class: " + Reflection.getOrCreateKotlinClass(Integer.class).getQualifiedName()));
     }

     var10000 = new String[]{"a", "b", "c"};
  }

  Integer[] test = (Integer[])((Object[])var10000);
  String var7 = "key2";
  Object[] defValue$iv = new String[0];
  KClass var4 = Reflection.getOrCreateKotlinClass(String.class);
  if (Intrinsics.areEqual(var4, Reflection.getOrCreateKotlinClass(Integer.TYPE))) {
     var10000 = new Integer[]{1, 2, 3};
  } else {
     if (!Intrinsics.areEqual(var4, Reflection.getOrCreateKotlinClass(String.class))) {
        throw (Throwable)(new IllegalArgumentException("Invalid class: " + Reflection.getOrCreateKotlinClass(String.class).getQualifiedName()));
     }

     var10000 = new String[]{"a", "b", "c"};
  }

  String[] test2 = (String[])((Object[])var10000);
}

It's pretty huge considering that function was called only twice with 2 cases in the "when" block. And it doesn't even do anything useful - you can already see the result of if cases.


Correct way to do it - declare each type as separate non-inline functions:

fun getArray(key: String, defValue: Array<Int>) : Array<Int>{
    return arrayOf(1, 2, 3)
}

fun getArray(key: String, defValue: Array<String>) : Array<String>{
    return arrayOf("a", "b", "c")
}

You have to write slightly more code, but it does not have any of 3 issues I mentioned above.

You get very clean bytecode that way as well (small size, high performance), this is decompiled bytecode of same example as before but using non-inline functions:

public static final void testArray() {
  String var3 = "key";
  Integer[] var4 = new Integer[0];
  getArray(var3, var4);
  var3 = "key2";
  String[] var5 = new String[0];
  getArray(var3, var5);
}
Pawel
  • 15,548
  • 3
  • 36
  • 36
  • Thank you for this answer. What would you suggest if I want to make the `defValue` parameter nullable? If I call `getArray("...", null)` it will result in `overload resolution ambiguity`. Do I really have to use different method names then? – Cilenco Oct 02 '18 at 05:01
  • @Cilenco `null` is ambiguous in that case and compiler cannot resolve which method you want to call. You have to cast it to desired nullable type, for example `getArray("...", null as Array?)`. – Pawel Oct 02 '18 at 09:30