I have a class that looks like: ```class Foo( ...
# getting-started
c
I have a class that looks like:
Copy code
class Foo(
    val a: Byte,
    val b: Byte,
) {
    constructor(bytes: ByteArray) : this(bytes[0], bytes[1])
}
The problem with this implementation is that if
bytes
is too small, the error message is not perfectly clear, and if it is too big, there is no error message at all. I can add an error message like so:
Copy code
constructor(bytes: ByteArray) : this(bytes[0], bytes[1]) {
        require(bytes.size == 2) { "…" }
    }
but that only protects against the case where the array is too large. How can I add some code that runs before
: this(…)
to add the check? The constructor must remain a constructor.
r
Don't use a primary constructor:
Copy code
class Foo {
    val a: Byte,
    val b: Byte,

    constructor(a: Byte, b: Byte) {
        this.a = a
        this.b = b
    }

    constructor(bytes: ByteArray) {
        require(bytes.size == 2) { "_" }
        this.a = bytes[0]
        this.b = bytes[1]
    }
}
3
c
🤦 of course. Thanks!
g
It's just my own curiosity, but why not to use fake constructors? Like this:
Copy code
class Foo(
    val a: Byte,
    val b: Byte,
)

fun Foo(bytes: ByteArray): Foo {
    require(bytes.size == 2) { "_" }
    return Foo(bytes[0], bytes[1])
}
3
y
Or, when they land,
Copy code
static operator fun invoke
which doesn't have any issues with imports or anything
👍 1
c
There are multiple reasons in this particular case, but one of them is binary compatibility because the constructor already exists, another one is that it's an abstract class and implementations exist that we don't want to touch
👌 1
I am definitely not replacing a constructor by the invoke hack
👍 1
y
It's kind-of becoming more official and less of a hack. In fact, they might be better in terms of binary compatibility (since a function is less strict than a constructor). Note the
static
there.
g
It may be influenced by my education, but I always thought
invoke
function is usually an antipattern. Fake constructor looks clearer to me. And I think, it has exactly the same compatibility problems as
static operator fun invoke
.
💯 2
c
@Ruckus However, that requires duplicating the contents of the constructor. If I add a third constructor with yet another first-step, it can't add its own checks before calling one of the first two.
r
Correct. Unfortunately there's no way around that that I'm aware of. Well, there is. But it's ugly:
Copy code
class Foo(
  val a: Byte,
  val b: Byte,
) {
  constructor(bytes: ByteArray) : this(
    bytes[0].also {
      // do validation here e.g.
      require(bytes.size == 2)
    },
    bytes[1],
  )
  // Other constructors
}
y
Or:
Copy code
class Foo(
  val a: Byte,
  val b: Byte,
) {
  constructor(bytes: ByteArray) : this(
    kotlin.run { // can't use run IIRC because uninitialized this. Kotlin please fix this 
      // do validation here e.g.
      require(bytes.size == 2)
      bytes[0]
    },
    bytes[1],
  )
  // Other constructors
}
k
Now Java 24 has flexible constructor bodies. If this Java feature existed when Kotlin was created, Kotlin would probably also have had flexible constructor bodies and this current issue wouldn't be an issue. Now Java has one feature that Kotlin doesn't have.