Kotlin [1..n] constructor parameter

Viewed 91

Is there a way to enforce 1..* parameters in Kotlin that will still allow the spread operator?

I've tried:

class Permission(
    // 1..n compliance
    accessiblePage: Webpage,
    vararg accessiblePages: Webpage
) {

And that does enforce 1..*, but it also means that Permission(*pages) won't work, so that's a pretty awkward interface.

Is there an easy way to enforce 1..* without a runtime constructor error?

1 Answers

There is, unfortunately, no way to check this in Kotlin at compile time aside from the way you mentioned. Since vararg parameters are really just syntactic sugar for an array, your code is essentially

class Permission (
    accessiblePage: Webpage,
    accessiblePages: Array<Webpage>
)

So the question then becomes "Can you ensure that an array has at least one element in it at compile time?" For most languages, that's a clear no, although the Kotlin team did at one point experiment with it:

[C]urrently, Kotlin compiler doesn't collect static information about collections size. FYI, at some point Kotlin team tried to collect such information and use it for warnings about possible IndexOutOfBoundException and stuff like that, but it was found that there were a very little demand on such diagnostics in real-life projects, so, given complexity of such analysis, it was abandoned[.] (https://github.com/Kotlin/KEEP/issues/139#issuecomment-405551324)

It's possible that this metadata will be added at some point, but you shouldn't expect it soon.

That said, you could always combine a runtime check in the case of an Array with an overloaded signature in the case of varargs. This would mean that your vararg example would work the same, but passing an array to the function would subject it to a runtime check (you'd also not have to use the spread operator anymore):

class Permission (
   accessiblePage: Webpage
   vararg accessiblePages: Webpage
) {
    constructor(accessiblePages: Array<Webpage>) {
        require(accessiblePages.isNotEmpty()) {
            "Must have at least one accessible page."
        }
    }
}

called like

val permission1 = Permission(Webpage(), Webpage())
val permission2 = Permission() // Would fail at compile time

val pages = arrayOf()
val permission3 = Permission(pages) // Would fail at runtime. Note also the lack of the spread operator.
Related