`listOf` creates a whole new list. Java's `unmodif...
# announcements
k
listOf
creates a whole new list. Java's
unmodifiableList
is a very thin wrapper that throws exceptions on trying to modify the contents of the wrapped list
e
just use
Collections.unmodifiableList()
if you're on JVM
k
That's not helping me here. I'm looking explicitly for the Kotlin way
n
unmodifiablelist is honestly pretty hacky. if you want a nice, kotlin solution I'd suggest looking at kotlinx.immutable.
but I'm not sure it meets your criteria of being a simple one liner
e
(that's immutable, not unmodifiable view)
I mean, IMO kotlin way would be don't reinvent what you don't have to
n
ah alright didn't realize that unmodifiable list doesn't protect its contents
a
Don't cast List to MutableList. It may not succeed.
n
Then I think this is the same as our discussion from the other day, right, ephemient? If you write a new class and use delegation, then the cast fails on the latest kotlin bytecode right?
k
I can't prevent the callers from casting back to
MutableList
. That's the whole point here. I want a one liner that creates a thin, unmodifiable view on the original list. If there's no such thing, just say that.
e
JVM/IR, but that isn't fully ready yet
n
@Kirill Grouchnikov it may not work 100% now, the most future proof way I think would be to write a simple one line class via delegation:
Copy code
class UnmodifiableList<T>(val list: List<T>) : List<T> by list
a
There is no such thing in stdlib. But listOf can't be cast to MutableList under new IR.
n
This actually prevents callers from casting
a
Also Kotlin playground demonstrates this, it won't cast
n
listOf can't be cast either in new IR? That's surprising
good to know
k
listOf
is too heavyweight as it creates a whole new list and takes up a separate chunk of heap
e
@Nir: even on the new IR, List/MutableList has special guards, but the common idiom doesn't wrap the iterators so they can still be cast if you assume malicious users
n
I guess it depends how far you want to go in fighting Machiavelli
e
a
@Nir actually I'm not sure now, need to check)
k
I always assume malicious users when I'm writing library code
Or not even malicious, but rather non-compliant for whatever reason
n
@Kirill Grouchnikov if you use UnmodifiableList above, then probably in the next few months as you upgrade Kotlin, move to new IR, it will prevent the cast from being successful
I dunno if you assume that then can't people just break everything using reflection?
k
That would be highly intentional
e
if you have Java users, then I can understand being defensive about List/MutableList, because the distinction is not visible to them
k
This is evolving into a theoretical discussion that I'm not that interested in. From what I'm reading here, the answer to my question is that there is no such one-liner thin wrapper right now.
e
however, if you are all Kotlin, I believe you should not bother. an List→MutableList cast, like reflection, is explicitly asking for it.
4
v
That's not really right. Most beginners learn how to cast, but not how to do reflection. And if they can cast, they will, even if there would be proper and safer methods for doing the same. That's not an assumption but a sad observation.
👍 1
n
For me it's not a matter of knowledge, but intent. In C++ people often use the "protect against murphy, not machiavelli" which I think is a good adage. Protect vs things that people could do by accident typically because it looks a lot like common usage (like mutating a list), don't try to protect against people doing things deliberately that could break them by their nature
1
Is it even practical/possible to write a whole Kotlin library, "protecting" your users against downcasting? I think it's impossible to do broadly even in theory.
I guess you could do it if you never ever return any interfaces or open classes to your users, ever, if that is actually practical for your library.
z
object : List<Foo> by yourListOfFoos {}
is one line and hides the underlying type like you want.
v
Copy code
fun <T> List<T>.asUnmodifiableList() = object : List<T> by this { }

val unmodifiableList = mutableList.asUnmodifiableList()
e
note my earlier pl.kotl.in. yes, but that is easy to get around by casting the iterator. how far are you willing to defend against?
also breaks equality and hashcode and tostring...
v
Well, one has to die one death 😄
n
@Zach Klippenstein (he/him) [MOD] yep that was my suggestion as well, and in an earlier thread with ephemient, but this doesn't actually work unless you're on the new IR
(ephemient is the one who pointed this out to me)
e
well, it works in that it prevents direct modification. but it doesn't prevent the upcast in Kotlin.
z
It prevents the cast for me, without IR. It’s an entirely different type, it can’t be cast to
MutableList
.
n
that was my reasoning as well, but ephemient said he tried it and was able to do the cast locally (I didn't try, only on the playground which is using IR) 🤷
z
i just did it locally, it definitely doesn’t work. I don’t see how it could –
object :
creates a new type, and completely unrelated types can’t be cast to each other on the JVM. Unless we’re trying subtly different code?
n
hmm I was doing
class foo(val x: List<Foo>) : List<Foo> by x
i.e. class instead of object
v
Copy code
fun <T> List<T>.asUnmodifiableList() = object : List<T> by this {}
 
 val mutableList = mutableListOf(1, 2, 3)
 println(mutableList)
 mutableList.add(4)
 println(mutableList)
 val unmodifiableList = mutableList.asUnmodifiableList()
 println(unmodifiableList.toList())
 mutableList.add(5)
 println(unmodifiableList.toList())
 (unmodifiableList as MutableList).add(6)
 println(unmodifiableList.toList())
Copy code
[1, 2, 3][1, 2, 3, 4][1, 2, 3, 4][1, 2, 3, 4, 5]java.lang.ClassCastException: Line_0$asUnmodifiableList$1 cannot be cast to kotlin.collections.MutableList
e
yeah, fails in Kotlin REPL
but if you actually try to compile it, the cast succeeds
n
@ephemient I'm trying it locally and it fails
e
Copy code
fun main() {
    object : List<Any> by emptyList() {} as MutableList<Any>
}
Kotlin 1.4.10 here, I'll give 1.4.20 a try in a sec
n
I'm on 1.4.0 it looks like
according to my build.gradle anyway
v
Copy code
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3, 4]
[1, 2, 3, 4, 5]
Exception in thread "main" java.lang.ClassCastException: de.empic.teamcity.config.FooKt$asUnmodifiableList$1 cannot be cast to kotlin.collections.MutableList
	at de.empic.teamcity.config.FooKt.main(Foo.kt:14)
	at de.empic.teamcity.config.FooKt.main(Foo.kt)
From a compiled .kt file
1.3.70 actually
e
hmm, I do not know what is up with this 🤷
in any case, I don't think it's worth it. do the whole unmodifiable wrapper right, or don't; half-measures mean you fail in harder-to-anticipate ways
👍 1
n
This specifically I don't think is worth it because it gives you almost no real protection
But, a real
ImmutableList
that defensively copies the constructor, or otherwise provides functions where it knows it's "safe" is well worth it
Having a class with a
List
member that gets modified from halfway across your program, is very very well within the realm of Murphy
I had a bad bug from this in python literally yesterday
v
Immutable is totally different to unmodifiable vie though.
n
Yes, agree