Why Kotlin functions returning List do not use immutable list implementation?

Such as those readily available in Java,e.g. List.copyOf(collection), or similar native implementation. In particular, I would expect, that toList(), calling on a collection or a sequence, would return an immutable snapshot of that collection or sequence, and calling toList() again would return the same instance.

A related side question - what is the idiomatic solution to frequent situation, when you get List in a constructor, and you want to store (a copy) of this (seemingly unmodifiable) list?

In Java, I would go with something like this:

class Service {
    private List<String> list;

    public Service(List<String> list) {
        this.list = List.copyOf(list);
    }
}

In Kotlin, I can do either:

class Service(val list: List<String>) {
    ...
}

or

class Service(list: List<String>) {
    val list = list.toList()
    ...
}

The problem is, that the first solution is unsafe (must make an assumption that the caller would not modify the list), while the second solution is inefficient (every such call copies elements around and creates another snapshot of the collection).

Which solution would you use? I would consider the first solution as idiomatic, and would use in my code where I can make that assumption.

However, if I would like to create some independent library, that makes no assumption about outer environment, I would need to resort to the second solution, wouldn’t I?

Or is there something better? Isn’t this a design flaw of the Kotlin collection framework?

I guess they have gone to the trust everything path.

The constructor of JsonObject in kotlinx-serialization-json takes any arbitrary Map<String, JsonElement> as its backing map.

But you are right! why trust here but not in toList() implementation :thinking:

I guess this is why they thought about these:

1 Like

I agree that there should be a better solution, but I think the reason that this is not addressed is that typical Kotlin code doesn’t contain many mutable lists, especially not passed around as arguments. E.g. if I see in code reviews someone use a mutable list just to fill it more easily (which means “more like I used to do it in Java” in this context), I try to convert it to immutable style, or at least use buildList{...}. So if you and your team follow best practices, you can assume that lists are really immutable. It’s not ideal to rely just on conventions, so you have to use the toList() solution when you want to be 100% safe.

So you think that the idiomatic Kotlin solution of this usecase is just to trust the code. That is, if someone just proclaims MutableList to be a List, pretend that this list is unmodifiable. Not great, you would probably get away with this technique in most cases, but I am afraid it could backfire sometimes.

In the good old Java days, I have seen pedantic implementations that would copy a snapshot of the input, and if they should expose the list, they would do so using the unmodifiable wrapper. I thought that these days are gone starting with Java 10 where new unmodifiable lists were introduced, which provide a clean solution to this problem.

So there is a better solution, you could just implement Iterable.toList simply as:

public fun <T> Iterable<T>.toList() = java.util.List.copyOf(this)

That would create immutable snapshot as requested, and you would get that idempotency optimization for free.

Problem is that this construct does not support null values, maybe another reason was that Kotlin collection framework library needs to be compatible with Java prior 10.

So more robust solution would be reimplementing these immutable collections in Kotlin, I think it wouldn’t be too much work. I would consider such solution much better than the current one.

Btw, the contract of Java 16 Stream.toList, requires that the implementation of the returned list must be unmodifiable.

The returned List is unmodifiable; calls to any mutator method will always cause UnsupportedOperationException to be thrown.

And AFAIK the default implementations of stream return the immutable snapshot.

Have you had a look at kotlinx.collections.immutable yet? I believe their ImmutableList is exactly what you want!

Have you had a look at kotlinx.collections.immutable yet? I believe their ImmutableList is exactly what you want!

Well, yes, it seems it is exactly that. This is the very same example I provided:

My point, however, was having this feature out-of-the-box, or idiomatic practice, not as some (more or less non-) standard extension. After all, you need to use .toImmutableList() to opt-in for this feature.

But yes, it is very close to what I want. Thanks for the link!

I also wonder what means that this is a “proposal”, and since it is in kotlinx package, it is (or soon will be) after all some sort of standard extension?

If I understand correctly, the main reason is that Collections.unmodifiableList change referential equality and the Kotlin team didn’t want the syntax to change referential equality invisibly.

This question is asked once in a while, and there is a youtrack ticket: https://youtrack.jetbrains.com/issue/KT-66257/Compiler-option-for-protected-passing-of-collections-to-Java

1 Like