If I have a `MutableList`, and I expose it to anot...
# announcements
l
If I have a
MutableList
, and I expose it to another function with just the
List
interface, can that function cast it to
MutableList
? At what point is something cast-able to its original type? I'm not new to generics but I haven't thought about that before.
e
yes, it can be casted
any object instance never changes its class (raw type, without type parameters... although arrays are special, because of historical Java reasons) regardless of what it's been upcast to
l
I see, and I assume that's why functions like
asList()
exist, to copy it into another new instance?
e
they do copy into a new instance
however, on the JVM, the distinction between List and MutableList only exists in Kotlin source code
at runtime there is no difference, so in practice
listOf(1, 2, 3) as MutableList<Int>
succeeds
so… don't do that
👍 1
(both
kotlin.collections.List
and
kotlin.collections.MutableList
map to
java.util.List
)
s
IMO callers taking the risk to cast down from
List
to a more concrete type are breaking their end of the API contract and are treading into “undefined behavior”-style waters anyhow
like first off the API makes no guarantee what the implementing type is so if you return an
UnmodifiableList
and they try to cast it to a
MutableList
they’ll get a nice little runtime error unless they cast with
as?
and handle the null somehow
e
... they won't, because List and MutableList are literally the same platform type at runtime.
UnmodifiableList(...) is MutableList<*>
s
oh rip, my b
n
One thing that is also worth keeping in mind is that even without casting, even without using Java, if you have a
List
that's "really" a MutableList, somebody else can just mutate it
s
I guess that’d only apply if they tried to cast it to ArrayList or something
e
an UnmodifiableList will throw UnsupportedOperationException if somebody tries to mutate it
n
if you're a function accepting a
List
this is less of an issue but if you are a class with a
List
member, then you need to be aware of it
around this point though may just want to look into kolinx.immutable
e
right. if you have a List, or even a even UnmodifiableList, that doesn't mean the list won't change. it just means that you shouldn't modify it
l
Going back to java era, casting was always understood as trying to gain access to more features. It only started with Kotlin that interfaces are used to restrict access as far as I know.
s
shrug modifying lists received from other scopes is a code smell anyhow imho, I try to call that out whenever I see it unless it’s an instance with trivial visibility as to where the list goes
e
Scala has a whole collections hierarchy (...well, several...) with separate mutable and immutable types too
n
i mean, I'm primarily a C++ programmer, not a Java programmer, but my understanding is that downcasting is mostly considered smell, in general
2
and has been for a long time
e
they don't use the Java collections so they can actually distinguish between (Immutable)List and MutableList
however, Kotlin stuck to Java collections for compatibility and low overhead, so that's what we have
n
Well, I think having a read-only list view makes sense
regardless of what it's called
i think, you need all 3; read-only list, mutable list, and immutable list
e
sure, but whether you have the ability to downcast it to mutable list or not is different between Kotlin and Scala
different trade-offs involved
n
I'm not sure I follow, what are the different trade-offs? It sounds like Scala just named its ImmutableList, List, and doesn't have a read-only List
and that's all
e
Kotlin: List() as MutableList always succeeds, because it's the same platform type
Scala: List and MutableList have no relation to java.util.List
n
yeah, List as MutableList always succeeding is pretty bad.
A huge chunk of Kotlin's standard library functions that return
List
should really be returning
ImmutableList
instead, but I guess like you say it isn't done for Java overhead reasons
e
also compatibility. if it weren't a java.util.List, you couldn't easily pass it to Java
n
I was going to suggest to kotlinx.immutable a package like that, where users could do
import kotlinx.immutable.stdlib_extensions.*
or something like that
and now things like
+
and
map
return ImmutableList instead of List
e
... and they'd be incompatible with other Kotlin code as well
n
it wouldn't be incompatible, unless said code is being naughty and downcasting
ImmutableList
inherits from
List
make a function return a more-derived type isn't a breaking change
e
if it inherits List, then it can be typecast to MutableList on JVM, end of story.
if ImmutableList inherits List, the only option is to blow up at runtime if somebody does that
n
I don't follow; if you try to cast
ImmutableList
to
MutableList
using
as
you'll get an exception at runtime
just like any unrelated cast?
class ImmutableList<E>(val x: List<E>): List<E> by x
afaics casting ImmutableList to
MutableList
gives an error the same as casting ImmutableList to Int
e
nope, because both MutableList and List are represented by java.util.List
the bytecode for casting to List and casting to MutableList is exactly the same
the distinction is completely erased at runtime
n
s
Maybe a silly question, but is it possible that this breaks on the Kotlin side but not if you did it in Java?
n
you mean that the cast breaks?
s
Yes
e
ok, if that cast breaks, that means ImmutableList cannot be passed as a list to Java
n
could be I know very little about Java 🙂
e
that is the other possible trade-off
n
@ephemient yep, never claimed otherwise
e
and it also cannot be passed to Kotlin code expecting a kotlin.collections.List
n
what
it already is a List there
e
oh the playground didn't load the first time for me, I thought you meant kotlinx.collections.immutable
n
kotlinx.collecitons.immutable inherits from List
so it can be passed wherever List is expected
e
ok, this is not what I expected and I am digging into the bytecode.
n
Will be curious to hear what you find. At any rate though, it does mean you can get a pretty nice, sane model of the world in Kotlin I think as long as you don't care about Java
e
... playground must be doing something different, the cast succeeds if I run locally. I'm also on 1.4.10
the cast succeeds on the new IR backend as well
I honestly have no idea why the cast fails on playground
n
hmmm... that's really unfortunate
I'll try to run locally, now I'm curious
that said, I still think it's reasonable to say that changing e.g.
map
to return
ImmutableList
is not "really" incompatible with other Kotlin code,
ImmutableList
is still a sub-type of
List
so it should work. The only code it will break is naughty code that is downcasting and operating on the MutableList
this all makes me feel like if you wanted to do a pure kotlin project, and didn't mind a moderate performance hit, it would be really nice if there was a package that just "replaced" the standard collection library and cleaned up all this stuff
e
ok, I figured I was setting new IR mode wrong, and it fails in new IR.
n
ah that's great
e
looking at the bytecode, it's translating the cast into an out-of-line call
TypeIntrinsics.asMutableList()
so that's another tradeoff too
n
yep. It is nice though that while making trade-offs, Kotlin was able to keep some basic sanity
so it still has (possibly irreconcilable) corner cases with Kotlin classes extending Java classes
but it's probably good 90% of the time
a
I think a
List
can always be casted to
MutableList
only in JVM. And if you write your own implementation of the
List
interface (not
MutableList
), then any attempt to mutate the list will result in
NotImplementedError
being thrown. In non-JVM targets the cast may fail at runtime. I think this happens in Playground, because it is probably based on Kotlin/JS. https://pl.kotl.in/xWXTWNGzm
e
tl;dr the cast always succeeds in Kotlin/JVM, then fails at runtime with an exception. it seems to fail in Kotlin/new IR, which Playground has for both JVM and JS