Local functions

There is a kind of function we did not cover in the first article: functions that are declared inside other functions, using the regular syntax. These are called local functions and they are able to access the scope of the outer function.

fun someMath(a: Int): Int {

fun sumSquare(b: Int) = (a + b) * (a + b)



return sumSquare(1) + sumSquare(2)

}

Let’s begin by mentioning their biggest limitation: local functions can not be declared inline (yet?) and a function containing a local function can not be declared inline either. There is no magical way to avoid the cost of function calls in this case.

After compilation, these local functions are converted to Function objects, just like lambdas and with most of the same limitations described in the previous article regarding non-inline functions. The Java representation of the compiled code looks like this:

public static final int someMath(final int a) {

Function1 sumSquare$ = new Function1(1) {

// $FF: synthetic method

// $FF: bridge method

public Object invoke(Object var1) {

return Integer.valueOf(this.invoke(((Number)var1).intValue()));

}



public final int invoke(int b) {

return (a + b) * (a + b);

}

};

return sumSquare$.invoke(1) + sumSquare$.invoke(2);

}

There is however one less performance hit compared to lambdas: because the actual instance of the function is known from the caller, its specific method will be called directly instead of its generic synthetic method from the Function interface. This means that no casting or boxing of primitive types will occur when calling a local function from the outer function. We can verify this by looking at the bytecode:

ALOAD 1

ICONST_1

INVOKEVIRTUAL be/myapplication/MyClassKt$someMath$1.invoke (I)I

ALOAD 1

ICONST_2

INVOKEVIRTUAL be/myapplication/MyClassKt$someMath$1.invoke (I)I

IADD

IRETURN

We can see that the method being invoked twice is the one accepting an int and returning an int , and that the addition is performed immediately without any intermediate unboxing operation.

Of course there is still the cost of creating a new Function object during each method call. This can be avoided by rewriting the local function to be non-capturing:

fun someMath(a: Int): Int {

fun sumSquare(a: Int, b: Int) = (a + b) * (a + b)



return sumSquare(a, 1) + sumSquare(a, 2)

}

Now the same Function instance will be reused an still no casting or boxing will occur. The only penalty of this local function compared to a classic private function will be the generation of an extra class with a few methods.