Object immutability

Hi everyone,

I’m relatively new to Kotlin, and I’ve already rewritten big portions of production code to it because it’s a really awesome language. As of now, I’d only miss a recursive immutability modifier, like const in c++.

There’s a small discusson on that in the slack’s #language-proposals channel, and thanks to some contributions (mostly arguments against my proposal) I’ve been able to develop it better, so I’ll give it a try here.

That modifier would be able to target

  • Methods/functions: to tell they are read-only. In the scope of such methods, all members are treated like vals so they can’t be set, and only their read-only methods can be called.
  • Variables: to identify them as immutable. Right now we have val to tell the reference cannot change, but the value is not readonly and you can perfectly mute the object it points to. By having that modifier we would not be able to call any setter nor any non-readonly function

Real immutability is a pretty important feature, Kotlin itself already has had to implement it for containers (with my proposal containers would be able to go back to having only 1 implementation handling both mutable and immutable references)

But there’s a catch in here: with only that (which would already be a pretty awesome language feature for a better typing and restrictions), we cannot assert that a immutable object will not be modified in another thread that has a mutable reference to it. In my opinion that’s not a good enough reason, but we could still do something about it in runtime.

Every object could have an atomic flag stack telling whether it’s in readonly state or not.
When an object becomes referenced as immutable, that flag would be pushed, and when leaving that reference’s scope it would be popped.
Then, whenever a mutating operation is performed on an object with that flag set, an exception would be thrown.
This could be optimized to have pretty much no overhead, but still it would be nice if it’s only enabled with a runtime flag.

What do you think about it?

EDIT: The compiler should be able to detect by itself when a method is readonly or not, so the method modifier could be just a hint for the compiler to throw an error when a method has that modifier but it’s mutating the object.

EDIT 2: With all that in mind, we could have a new concept: immutable classes. These would not need any locks whatsoever because they would be immutable by definition. We could have an immutable class modifier which asserts that for any class. Clear example: String.

3 Likes

I like the idea of an immutable class modifier, as this would definitely make some code better and easier to understand. The problem I see is inheritance. Should immutable classes be sealed all the time and if not, how do you prevent a child class from mutating the object? Obviously in Kotlin you can children of immutable classes to also be immutable, but what if someone in Java is trying to inherit from an immutable class.

The same problem exists the other way round? Should immutable classes be able to inherit from mutable classes? How can you than ensure that they are really immutable? I don’t think there are any good ways to solve this problems if it is even possible (on the JVM that is). I don’t think this would be possible as long as JVM byte code does not support immutability on an object level.

Initially, the immutability modifier applies only to references/variables. Then, as an extra idea, we can have immutable classes as well. Regarding that, I would vote to force children to be immutable if the parent is, but that definitely needs lots of thinking.

As of java interop, I didn’t really think a lot about it. I would do like with null safety, which is basically that there are some annotations helping intellij to warn java coders, but with no way to enforce anything. And the other way around, if something comes from java, runtime checks would have to come in place.

I just don’t understand how this should work? I explained why immutable classes would not work. The problem I see with the immutable references/variables is that this would be impossible to do on the JVM. I don’t see any way how this could be solved in any way. I understand you idea with a flag for each object but again, this would only work for parts of the code which do not use any third party libraries.

Again, as with null safety, there’s no way to ensure it in java, but that didn’t stop the feature from being implemented. Look, a fragment of the documentation stating why a npe could still occur:

Java interoperation:
Attempts to access a member on a null reference of a platform type;
Generic types used for Java interoperation with incorrect nullability, e.g. a piece of Java code might add null into a Kotlin MutableList, meaning that MutableList<String?> should be used for working with it;
Other issues caused by external Java code.

The same would apply to immutability. Adopting it would become a very slow process, but eventually most libs will have their kotlin version :slight_smile:

I don’t agree. With Null safety you know when an npe can happen, as they are restricted to platform types. That would not be so with immutability as any open class could become mutable at any time. There is no way of writing runtime checks against it, which would leave you with the same problem you had before.

I’m not saying I don’t like the idea. I just think it wont happen :frowning:

As to the libs having a kotlin version. I am 100% sure that as long as Kotlin will not fully replace Java, most big libraries will stay Java and this includes a lot of the standard library (maps, lists, etc) as JetBrains will not reimplement them in Kotlin if they are already existing in Java.

Immutable classes could be translated as final for java. And again, what you’re exposing is only one of the so many problems java interoperability would bring to the feature, but as I said it could still exist and make kotlin-only projects very strongly typed.

Kotlin is so close to be the perfect language, and by seeing its path with kotlin native and javascript, I would be so sad to see that the JVM limitations are blocking its evolution.

Hi @pcasafont,
your proposal is interesting, but I am really sceptic about it.

But there’s a catch in here: with only that (…), we cannot assert that a immutable object will not be modified in another thread that has a mutable reference to it. In my opinion that’s not a good enough reason, but we could still do something about it in runtime.

I agree with you, for this reason you should not mix up immutability with read-only.

Every object could have an atomic flag stack

You need an atomic counter?

When an object becomes referenced as immutable, that flag would be pushed, and when leaving that reference’s scope it would be popped.

Two atomic operations for each variable?

This could be optimized to have pretty much no overhead

How it is possible optimize atomic counters without disabling it?

What do you think about it?

C++ const keyword is an unfortunate feature, API design become harder and read-only doesn’t mean immutable.
Immutable data class looks as safer choice.

1 Like

The second part of my proposal is only to be able to assert the immutability is true along threads. If we decide to give up this assertion, it wouldn’t be necessary.

However, let’s talk about it. When I said it could be optimized it’s because of several facts:

  • Some variables can be statically proven to be thread-local. These wouldn’t need any runtime checks.
  • Variables/values created as immutable will have no possible way to be ever referenced as mutable. These won’t need checks either.
  • The counter (yes, you’re right, I meant a counter) will only be updated when moving from mutable to immutable context.

Another way to solve all this would be to make casting from mutable to immutable impossible, and only allow objects which were directly created as immutable. That would make the feature thread-safe at compile time, but in certain cases we would have to copy objects just to pass them to functions that take only immutable parameters.

And there’s another decision I’m not sure about. Should immutability be totally recursive or just down to the class fields’ level? A clear example of just one level of immutability are containers: we may be interested on having immutable containers holding mutable objects… or the other way around.

Decisions, decisions… thanks for your feedback. By the way, how do you distinguish immutable from read-only?

This is probably the safer and cheaper way.

val mutable: MutableList<Int> = mutableListOf(1, 2, 3)
val readOnly: List<Int> = mutable
val immutable: List<Int> = Collections.unmodifiableList(listOf(1, 2, 3))

Indeed, and it would be a very good start.

I’d say they are conceptually the same, both readOnly and immutable are restricted to changes on compile time by kotlin, while the last one also holds runtime checks because java has a different interface for list and it needs runtime checks.

While they both have different implementations (the readonly is certainly mutable when it’s referenced to as a MutableList), here we encounter that interesting fact: one can cast from immutable to mutable containers with the current design, but with my proposal that wouldn’t be possible.

I wonder why there is no equivalent keyword in Kotlin like C++ const or Swift mutable func. I think it is missing another keyword in kotlin that does not allowed to call setter on const/val data. It’s weird to read in a code :

val list = ...
list += 5

The += operator should not be authorized for val data. Maybe we need something like:

val readOnly = mutableListOf(1, 2, 3, 4)
val readOnly += 5 // Forbidden call of plusAssign because of mutability
mutable val mutableList = listOf(1, 2, 3, 4)
mutableList += 5 // Allowed due to the mutable keyword

The mutable keyword indicates that we allowed to change the inner state of the object.

It could works with objects Interfaces if we have:

interface MutableList<T> {
    mutable operator fun plusAssign(value: T) = TODO("Do stuff")
}

Here we can know if a mutable val or a simple val can call the method. It may also works with custom classes and interface. All val objects should not call mutable marked properties or functions when mutable objects may call its mutable fields or functions.

With this there is not needs to write to interfaces for each collections with Mutability/Non mutability specification and for the call we may use the keyword before the arguments like:

fun foo(mutable list: List<T>) // we may modify the list but not the inner objects
fun foo(mutable list: List<mutable T>) // we may modify the list and the inner objects
fun foo(list: List<mutable T>) // we may modify the inner objects but not the list
fun foo(list: List<T>) // we cannot modify the list

The key word will create a fictive hierarchy as:

val t: T
mutable val t: T // is in fact Mutable<T> : T
class Child: T
val c: Child // c inherits from T
mutable val c1: Child // c is Mutable<C> and inherits with covariance

Why do you think about it ?

A generic way to split each mutable class into a readonly interface and a mutable interface, like it’s done for List/MutableList is probably a good idea, but I would prefer to see more immutable classes like Guava ImmutableList instead.

I wonder what are the chances of this idea moving forward after 2 years. With the strong focus on multiplatform it has more chances to be revisited?

Not easy to guess; Java interop is (was ?) a boost for Kotlin and in same time a brake for some features ( immutable, reified type in non inline function, union type, type constructors …).
That says, it would be nice to have a “const” C++ equivalent in Kotlin

fun xx(const x : MyClass)  // xx can only call const functions of MyClass class for x.
{ ... }

fun other() : const MyClass { ... } // return an immutable instance of MyClass.
     
class MyClass() {
   const fun myFunc() : Int {  // myFunc is defined as const, does not modify this state.
   }
 }

This assure that x object will not be modified by xx function but not this object won’t be modified by another process (that’s more role of Lock classes).
This similar to ‘double interface’ system, but simpler.

1 Like

This was my initial suggestion back in the slack discussion, but I got sidetracked into thread safety because as someone very well stated, smart casts would be out of the question.

Example:

class Foo(var x: Int? = null)

fun bar(foo: const Foo) {
    if (foo.x != null) {
        println(foo.x + 1) // ERROR: cannot smart cast foo.x to non-null Int
    }
}

That happens because despite foo being const, its x may still be modified in some other thread which has a non-const reference of foo.

You should contribute to KEEP-51. I think it became realistic since JVM features records now. But, of course we should wait until 1.4 release.

1 Like

I dont understand,
even without ‘const’ keyword, x may be modified in some other thread, and there’s no pb with smart cast ?

fun bar(foo: Foo) {
    if (foo.x != null) {
        println(foo.x + 1) // here, it can cast even if x can be modified in another thread.
    }
}

https://play.kotlinlang.org/#eyJ2ZXJzaW9uIjoiMS4zLjUwIiwiY29kZSI6ImNsYXNzIEZvbyh2YXIgeDogSW50PyA9IG51bGwpXG5cbmZ1biBiYXIoZm9vOiBGb28pIHtcbiAgICBpZiAoZm9vLnggIT0gbnVsbCkge1xuICAgICAgICBwcmludGxuKGZvby54ICsgMSkgLy8gaGVyZSwgaXQgY2FuIGNhc3QgZXZlbiBpZiB4IGNhbiBiZSBtb2RpZmllZCBpbiBhbm90aGVyIHRocmVhZC5cbiAgICB9XG59IiwicGxhdGZvcm0iOiJqYXZhIiwiYXJncyI6IiJ9

I still need to read through this thread in more detail, so apologies if this is only partially relevant.

But, to guarantee people using my framework correctly extend base classes—as immutable—I originally did a runtime check using reflection to verify immutability.

I have now moved this functionality to a static checker as a plugin for detekt: GitHub - cph-cachet/detekt-verify-implementation: A detekt plugin to enable static checking of concrete classes according to annotations on base classes.

It is not feature-complete yet (it doesn’t catch all mutable cases), but neither does it pollute the code base or runtime. Running the checker for extending classes is requested by applying an annotation to base classes. To me it seems like something like this (a ‘immutable’ keyword on base classes) would make sense as part of a language, but I need to read deeper into it to figure out arguments for/against.

Either way, the static checker might be of use to others. :slight_smile:

1 Like