So, what are the issues with these constrains?

Sadly, many. While the constrains themselves are sensible and very interesting, their implementation in the Kotlin/Native compiler & runtime is problematic. Here’s why:

1: These limitations are Kotlin/Native only

Kotlin/Native has value only as part of the Multiplatform experience. Sad but true. There’s no iOS programmer that makes an iOS only app in Kotlin rather than in Swift. Nor there is a high performance server programmer that will favor Kotlin/Native over C++ or Rust. Same goes for high performance desktop apps.

The only domain where Kotlin/Native might get some traction on its own is embedded. And that’s far from done.

Because those limitations are Kotlin/Native only, they are pushing programmers away from the Kotlin Multiplatform experience. Most of them are discouraged by the idea of having to rethink their entire architecture when they were trying to port an existing working code.

Also, because the corresponding API only exists in Kotlin/Native, this makes almost impossible to write some libraries with common code & common semantics.

2: These limitations are pure. We are practical.

The first time you start thinking in Actor Based Programming, your mind blows. You feel enlightened, intelligent. Breaking your code into actors means compartmentalizing resources and responsibilities. Each actor is atomic in its function, and every actor can communicate to other actors for requests that are outside of their atomic function. This is a very interesting, safe, and great way of coding… Until you start benchmarking.

Please understand that I am far from those who think that #PerfMatters everywhere, every time. I am more of an “efficiency over performance” guy. However, there are times where performance does really matter. Everything depends on the context, and we cannot rule out the necessity of performance in critical paths.

Have a look at this benchmark. Here’s what it prints:

With Mutex:

Completed 1000000 actions in 661 ms

Counter = 1000000

With Semaphore:

Completed 1000000 actions in 413 ms

Counter = 1000000

With Lock:

Completed 1000000 actions in 81 ms

Counter = 1000000

With Actor:

Completed 1000000 actions in 576 ms

Counter = 1000000

System Locks are 7 times faster than actors. This may be a big deal.

If the shared resource is not accessed by a critical path, then the performance of actors is absolutely fine. If it is, though, that’s problematic.

That’s because there’s only one actor that’s confined to one thread. The beauty of Actor Based Programming is also its weakness: every time you need to access the resource, there’s a message passing between threads.

Actors are great most of the time, but not every time.

Oh. This benchmark does not even show the benefit of using a ReadWriteLock as it is very context dependent, but with few writes and lots of reads, using such a lock can further increase the performance factor over actors.

3: These limitations are enforced at runtime

A code that compiles and works fine in JVM or JS will crash at runtime in Native.

All it takes is 5 lines of code:

object Foo { var bar = 21 }

fun main() {

Foo.bar = 42 // Crash happens here!

println(Foo.bar)

}

Remember? This is the work of “top-level objects are frozen by default” and “a frozen datum cannot be mutated”.

Because these limitations are enforced at runtime, neither the compiler nor the IDE will warn you that you are doing something verboten. It will compile, run fine in JVM & JS, and crash in Native.

In fact, there is no way the compiler & the IDE can warn you, because immutability is not part of the type system.