Why is method with vararg param selected when passing named arguments?

Consider this code:

class Log {

    private fun print(input: String, params: String) {
        println("${input.padEnd(15)}-> $params")
    }

    fun debug(input: String, vararg data: Any) {
        print(input, "vararg data: Any")
    }

    fun debug(input: String, data: Any) {
        print(input, "data: Any")
    }
}

fun main() {
    val log = Log()
    log.debug("data = [data]", data = arrayOf("data"))
    log.debug("[data]", arrayOf("data"))
}

The output is:

data = [data]  -> vararg data: Any
[data]         -> data: Any

The public methods on the class Log differ only in the type of the last parameter. One accepts a vararg of Any and the second one accepts a plain Any.

If an array is passed as a named argument, then the method with vararg is selected. When the argument is positional, the second method is selected.

Why is there a difference? Shouldn’t the compiler select the same method in both cases?

Both cases are different:

  • if you use no named arguments, the compiler will always prefer “normal” arguments over varargs, when possible

  • If you use named arguments, you have to pack the values for a vararg in an array, so you are effectively changing the signature to debug(input: String, data: Array<Any>), and that makes the vararg case more specific than having just an Anyargument.

I might need a little clarification.

What does it mean to be “more specific”? That the type is located lower in the type hierarchy?

I don’t understand the rationale behind this behaviour. Why is the second parameter treated as Array<Any> once and not both times? As I understand it, when the method with vararg parameter is compiled, the type of the last parameter will be Array<Any> no matter how it’s called.

I agree it’s a little confusing, but you really want the behavior that normal arguments are considered first, else you have problems writing code where the vararg version uses the normal one:

fun Box.add(item: Item) ...

fun Box.add(vararg items: Item) { items.foreach{ add(it) } }

On the other hand, if you use named arguments, you want that the vararg version can be called easily. If the rules were different, how would you even call the vararg version in your example? I don’t think there is a way to do this.

I think it comes do pragmatism and expectations. People from Java expect varargs to behave a certain way, but now also named arguments have to be considered, and must work somehow…

I think I understand your point.

Basically, if the call with a named argument didn’t call the method with vararg parameter, then there would be no way to call it since methods with a single element parameter is preferred over the vararg one when only 1 element is supplied.

What did you mean by being “more specific” though?

If you pass an Array<String> as an argument, and one signature accepts Any and the other one accepts Array<Any>, the second one is more specific.

If you really want to know the gory details: Kotlin language specification , in particular “11.4 Choosing the most specific candidate from the overload candidate set”

1 Like