kotlinbinary-compatibility

What does the term "Changing type from unsigned type to signed counterpart (and vice versa) is a binary incompatible change" in Kotlin documentation?


I'm a true beginner to Kotlin and I was wondering what the following term means in Kotlin documentation.

Changing type from unsigned type to signed counterpart (and vice versa) is a binary incompatible change.

In fact I've read about binary compatibility but here as a beginner I am asking for examples to understand binary compatibility in Kotlin intuitively.

What will occur if I use a code like this in a class, package or program written in Kotlin in different machines?

vaL a: UInt = 10000 
val b: Int = a.toInt()

Or a code like this

vaL a: Int = 10000 
val b: UInt = a.toUInt()

Solution

  • What will occur if I use a code like this in a class, package or program written in Kotlin in different machines?

    The code you provided here should work fine on every machine, even if you compile it only once on one machine. This is of course assuming you're compiling this Kotlin code for the JVM target (not native).

    When talking about binary compatible or incompatible changes, people usually refer to changes made to the code by developers, not conversions performed by the code. Also, it refers to changes made to public APIs of a compilation unit, not changes inside implementation code, nor running the same compiled code on different machines.

    So this piece of doc doesn't refer to Int<->UInt conversions like toInt() or toUInt() method calls. Instead, it refers to changing in the source code the type of something public, like a public function's argument or return value (from signed to unsigned, or vice-versa).

    For instance, assume you create a library with the following function:

    fun printMyNumber(n: Int) {
        println(n)
    }
    

    After releasing this, if you decide to change the type of n from Int to UInt, this is a binary incompatible change.

    This means that any piece of code that was compiled against the previous version of your library will fail when calling this function if you run it with the new version of the lib in the classpath. Users will have to recompile their projects against the new version of your lib if they want it to work correctly.

    When explaining it this way, it might seem like it's not a big problem, because upgrading a library in a project most likely implies recompiling the project anyway. The main problem occurs when there is a transitive dependency in the mix. For instance, assume the following dependency graph:

    project A
      \_ library B
      |    \_ library C (v1)
      \_ library D
           \_ library C (v2)
    

    In general this means that v2 of library C will effectively be used in the project. If library C introduced a binary incompatible change in the v2, you may have problems because lib B was compiled against v1 of lib C, and will fail if it tries to call functions that were changed like the one in my example above.