Access level of primary constructor of inline classes


#1

Inline classes cool feature, but there is some forbiddance I do not get why. The documentation says:

An inline class must have a single property initialized in the primary constructor. At runtime, instances of the inline class will be represented using this single property

So why is forced to have a public constructor? Ability to define private constructor could give a bit of flexibility.

For example, it would be nice to be able to use some preconditions for inline classes, I can imagine why inline classes can’t have to init block, but check could be done in a static method, something like this:

inline class URI private constructor(val value: String) {
  companion object {
    fun of(value: String): URI {
      check(value.isNotBlank())
      return URI(value)
    }
  }
}

Or an ability to “build” value, for example:

inline class URI private constructor(val value: String) {
  companion object {
    fun of(scheme: String, host: String, path: String): URI {
      check(scheme == "http" || scheme == "https")
      return URI("$scheme://$host/$path")
    }
  }
}

#2

In some situation the runtime needs to create real instances of the Inline Class. Similar to how Java automatically auto-boxes primitive values in some situations. I assume this is the reason for the public constructor.


#3

Interesting point. What kind of situations? Why they can’t be resolved via reflection?


#4

Generic types

If inline class type is used in generic position, then its boxed type will be used

See here: https://github.com/Kotlin/KEEP/blob/master/proposals/inline-classes.md#generic-types


#5

It’s not about generics.

The sidenotes here


explain the reason for the public constructor, no init etc.

In short an inline class is inlined in JVM method signatures, so from Java one can pass any instance of the wrapped type.


#6

How is that a reason to require a public constructor?


#7
inline class NonEmptyString(val s: String) {
   init {
       check(s.isNotEmpty())
   }
}
fun foo(s: NonEmptyString) = println(s.lenght)  // should never print 0

If you look at the function from java it looks like this

void foo(String s);

So you would then call

foo("") // prints 0, which goes against the implementation of NonEmptyString

Thereby breaking the inline class.
The reason as I understand it is as follows. Kotlin can not guarantee that a constructor will be called when an inline class is created. Therefor (in order to ensure that inline classes don’t break) they can not have any logic in their constructor. Also since you can create an instance of an inline class without calling the constructor there is no point in restricting the visibility of the constructor.


#8

via Java you can get any instance of the wrapped type.


#9

I see now, public constructor limitation exactly to prevent such fabric methods, because of being unable to call it when passing parameter. Thanks for clarification


#10

Wait, but current realizatdion do not preventing creating factories


#11

But you cannot be sure whether the instance came from your factory. Your factory might have been skipped by directly using constructor or providing parameter from Java code.


#12

Yeah, you right. Okay then