About init blocks in inline classes

Hi,

I just learned about Kotlin’s new inline class feature today. It got me excited right away, because it enables domain-driven design without the cost of the thin-wrapper-objects.

One thing that I find rather restrictive in the current specification is that an inline class does not permit an init block. I can imagine that there are several reasons for that:

  • The compiler requires the ability to wrap and unwrap the inline class as needed.
  • Side effects in the init block will lead to desaster and uncontrolled behaviour.

However, having no init block at all also removes an important capability of classes: parameter checking.

Let’s for example assume we want to create an inline class for URL, which wraps a single String. What would you typically do in the constructor? Check the URL syntax and throw an exception if it’s wrong. Unless I’m missinig something here, you can’t seem to do that with Kotlin inline classes.

If we ignore the possibility of side effects for a moment, what would happen if we allowed an init block for inline classes, and assume that the user only performs argument checking in that block? Well, sticking with the URL example, at some point the user has to do:

val url = URL("www.kotlinlang.org")

… which the compiler would translate to:

String url = "www.kotlinlang.org";
/* inlined parameter checks (init block) of the URL class here */

No harm done here. When the compiler runs into an explicit constructor call, it inlines the init block, if it needs to perform auto-conversion, it skips the block (because the constraints have already been checked at all locations where a new instance of the inline class has been created; they have been shifted to the system boundary, where they really belong). Side effects in init are the only real issue that I can see here; but that’s a massive anti-pattern anyway and definitly not a use case for inline classes.

I imagine that this could be very very useful. In Java, you are forced to decide:

  • either you just use String, and get weaker/ambiguous signatures (you don’t want just any String but a URL, but the type system can’t express that),
  • or you have to pay the overhead cost of the URL wrapper orbject.

Kotlin has the potential to allow for the best of both worlds, but at the moment unfortunately doesn’t offer this capability.

As a more syntactically restricted alternatives to init blocks, what if the kotlin compiler would insert checks for javax.validation-style field annotations? That way, we could perform range checks on numbers and length checks / regex checks on strings without needing an init block:

@Min(3)
@Max(20)
inline class Name(val s: String)

val name = Name("John")

… would compile to:

String name = "John";
if(name.length() < 3){
    throw new IllegalArgumentException("Name must have length 3 or more!");
}
if(name.length > 20){
    throw new IllegalArgumentException("Name must have length 20 or less!");
}

If the set of such check annotations is predefined, no side effects need to be considered.

Any thoughts?

1 Like

You are rewrote the section “Non-public constructors and initialization blocks” of KEEP/inline-classes.md at master · Kotlin/KEEP · GitHub

Thanks for the reference to that document; I only checked out the official docs I linked above. The paragraph you refer to is a bit inconclusive: does that mean we will get some form of init blocks for inline classes sooner or later, or has the idea been discarded and just put into the KEEP doc for reference?

The comments section of that keep is a better place to discuss Inline Class restrictions: Inline classes · Issue #104 · Kotlin/KEEP · GitHub

The last couple of comments refer to a ticket you are interested in:
https://youtrack.jetbrains.com/issue/KT-28056

Thanks, added a comment to the github issue. Will check those out first in the future.

The argument is that if your Kotlin code gets called from Java, the underlying raw type could be passed instead, and the init block won’t get called.

I think this is a good point, but could be something suppressable with an opt-in clause, because most of the times Kotlin code will call Java, but not the other way around, especially in active components.