Extension Constructors

Kotlin supports a great deal of extension functions, allowing us to customize classes from other libraries a lot. The only feature we cannot extend is constructors.

Constructors are commonly imitated in libraries like JetBrains Compose:

class MyFunnyClass(val string: String)

fun MyFunnyClass(int: Int?) : MyFunnyClass = MyFunnyClass(int.toString())

However, there are a few problems with this syntax:

  1. IDE highlighting - the IDE considers this a function, not a constructor. As such, custom theming (like bold constructors) doesn’t work, and the IDE complains the function starts with an uppercase letter.
  2. Repetitive - this declaration repeats the name of the extended class three times when using explicitApi.
  3. Java Interopability - Java will call this function MyFunnyClass(42), not new MyFunnyClass(42) or myFunnyClassOf(42).

I’m suggesting to introduce Extension Constructors, their syntax as such:

class MyFunnyClass(val string: String)

MyFunnyClass.constructor(int: Int?) : this(int.toString())
// or alternatively
constructor MyFunnyClass(int: Int?) : this(int.toString())

// and if you need more than one line...
MyFunnyClass.constructor(int: Int?) {
    println(int ?: 42)
    return this(int.toString())
}

Extension constructors always return the same type they extend.

Extension constructors won’t be able to use super(...), but only this(...), and won’t be appliable to enums and objects.

Extensions constructors can also be useful when creating default implementations for interfaces.

And for my third point - interopability with Java - I suggest adding a default @JvmName annotation:

@JvmName("myFunnyClassOf") // default, implicit unless overriden
MyFunnyClass.constructor(int: Int?) : this(int.toString())

As for interaction with the proposed context syntax:

context(Int?) // extension inner class?
MyFunnyClass.constructor() : this@MyFunnyClass(this@Int?.toString())

fun main() {
    val num: Int? = 42
    with(num) {
        MyFunnyClass()
    }
    num.MyFunnyClass()
}

Additionally, inline extension constructors will be especially useful.

3 Likes
class Test(val i: Int) {
  companion object
}
operator fun Test.Companion.invoke(val str: String) = Test(str.toInt())

// Test("10") == Test(10)

@amejonah That’s probably the best approach in the language as it currently stands — but it only works for classes that define a companion object (which most don’t). Also, it suffers from three of the problems Laxla mentions: the IDE will treat it as a function, not a constructor; it means putting the class name three times (or twice if you don’t make the return type explicit); and it might not return the expected class.

This proposal seems like an interesting idea. I can’t recall ever wanting something like it — but it seems clear, logical, unobtrusive, and would fit neatly with the existing language.

(I think I prefer the second syntax — constructor MyFunnyClass(… — as that seems more consistent both with how normal secondary constructors are defined, and with how it would be called.)

This syntax doesn’t solve the three issues I have mentioned - and won’t allow us to implement extension inner classes (will make a post about this soon, still thinking about it myself)

I didn’t think of it tho.

Could you provide an example? I think that would require an alternative syntax, because we intentionally removed the type name repetition.

While I think points 1 and 2 will would be possible with MyFunnyClass.constructor(...) = ... syntax, I doubt point 3 (interop with Java) would be possible. Even normal extension methods are only accessible to Java as static methods, any interop with extension constructors will be similar.

1 Like

Java interop here doesn’t mean we see such constructor as a regular constructor in Java. This is only about the name of the method - to start with a lowercase.

1 Like

If you want the constructor to appear as myFunnyClassOf, even in Kotlin that requires creating an entirely separate non-constructor method, so there would be no need for an extension constructor at all. If you want the new keyword to make it really look like a constructor, though, that’s not feasibly possible.

Please re-read the first post carefully. The only point of java interop in this topic is to have MyFunnyClass() in Kotlin (because it looks like a constructor), but myFunnyClass() in Java (like a method).

Or course, Java interop will be done using the identifierOf function name (for the class Identifier). See the documentation for @JvmName.

interface Registry {
    operator fun get(key: String)
}

internal class RegistryImpl { /* ... */ }

constructor Registry() = RegistryImpl()
// Or...
Registry.constructor() = RegistryImpl()

Note about the example right above this post: it doesn’t return RegistryImpl - as the implementation details shouldn’t be accessible. It returns Registry.

Note that namespace(static) extension is coming to Kotlin. Named constructor should be better.

I do not see how that is related to this topic. What’s a named constructor? How is it better? How does the introduction of extensions w/o companion objects affect it? To me, it is merely a bugfix. How does the introduction of identityless object affect it? @aurorabot99

It’s not practical because: on author side, you trade a little convenience (less typing) with worse flexibility (you’re forced to pass a big chunk of code into this(...) call before any data validation); on user side, it’s just a style issue.

Named constructor looks like List.of. It’s a normal function, so you can do whatever you want, and it could reflect the intent just by its name (like, to differentiate List.of(10) and List(10)). This is covered by the upcoming static extension feature, so I noted it here.

Personally, I don’t feel like constructor, as a concept, should appear in places other than within the class itself. Few language did that either I suppose.

1 Like

iirc, there’s a feature suggestion that fixes that?
Plus, that is a field of improvement to constructors, generally, and not something this suggestion should handle.

and List.of() is how we suggested it’ll be compiled to, JVM-side. It means you could call that instead of the fancy List() constructor.