benf.org :  other :  cfr :  Default arguments in Kotlin.

How does Kotlin generate default arguments?

It's interesting to look at the bytecode created by other JVM targetting compilers - how does feature X work?

I've been looking a little bit at Kotlin recently - which supports complex defaults, such as calling a function (even using previous arguments which may themselves be defaults), which aren't supported in Java.

Hang on, Java doesn't support defaults at all....

Nope - the usual pattern in java is heavy overloading / Builders. (Though Java 8 provides a recommended Optional<T> monad, but that's nothing you couldn't have done yourself in previous versions of Java.)

Fine, back to Kotlin.

Kotlin allows us to do this: (please forgive the terrible style!)

class frob2() {

    fun fred2(x: Int = 300, y: frob2 = mkFrob2(x)) {
        println("${this}${x}${y}")
    }

    fun mkFrob2(x: Int): frob2 {
        return this;
    }

    fun foobar() {
        fred2();
        fred2(100);
        fred2(100, frob2());
    }
}

At first glance, you might think this is managed by lifting the defaults to the call site, as it's done in some other environments.

That's great for constant arguments, but not very nice for complex things like y: frob2 = mkFrob2(x) above.

Decompiled frob2

If we decompile frob2 above, we get all this: (!)

public final class frob2 {
    public final void fred2(int x, @NotNull frob2 y) {
        Intrinsics.checkParameterIsNotNull((Object)y, (String)"y");
        String string = this + x + y;
        System.out.println((Object)string);
    }

    public static /* bridge */ /* synthetic */ void fred2$default(frob2 frob22, int n, frob2 frob23, int n2, Object object) {
        if ((n2 & 1) != 0) {
            n = 300;
        }
        if ((n2 & 2) != 0) {
            frob23 = frob22.mkFrob2(n);
        }
        frob22.fred2(n, frob23);
    }

    @NotNull
    public final frob2 mkFrob2(int x) {
        return this;
    }

    public final void foobar() {
        frob2.fred2$default(this, 0, null, 3, null);
        frob2.fred2$default(this, 100, null, 2, null);
        this.fred2(100, new frob2());
    }
}

So our function fred2 exists exactly as we might expect it to (along with some @NotNull annotations)... if we call 'fred' with the full set of arguments, we'll pass straight through, but there's an additional fred2$default function, which handles all cases where fred is called with default arguments!

What's that 4th argument, n2?

So as well as passing the 'default' for the type (0, or null), Kotlin passes a bitmask containing which arguments actually contain usable values!

A static bridge method? Really?

I found this really interesting - there's nothing in the class file spec to say bridge methods can't be static, but I've never seen one before - Javac only generates instance bridge methods to handle variance and visibilty issues - if you know different please shout!

Prior to CFR 0_124, bridge methods were hidden - because of the above, static bridge methods are now visible. (this won't affect Java, as we can't generate them anyway!)


Last updated 02/2018