Hey folks, question about `@UnsafeVariance`. If I ...
# library-development
t
Hey folks, question about
@UnsafeVariance
. If I am defining some
Container<out T>
class and I want one of its methods to return
Optional<T>
(for Java users), then is it safe/reasonable to do
Optional<@UnsafeVariance T>
? I don't want to do
Optional<out T>
since that will end up being
Optional<? extends T>
for Java users, which is annoying to work with. I think using
@UnsafeVariance
here makes sense because
Optional
is an immutable container so you can only get
T
out of it; you can't put a new
T
into an existing
Optional<T>
. It's just that Java obviously does not define
Optional
with
out T
at it's declaration (since it's not Kotlin) Am I making sense? Am I missing anything here?
👀 1
d
Is it possible to make the method for Java users an extension function instead? Example:
Copy code
import java.util.Optional

class Container<out T>(val v: T?)

fun <U: Any> Container<U>.optional(): Optional<U> =
    Optional.ofNullable(v)

fun main() {
    val c = Container(3)
    println(c.optional())
}
t
Ah, interesting. Do extension functions get exposed to Java users as functions on the class though? I would have assumed they get exposed as standalone functions that take the receiver as the first parameter or something
d
I would have assumed they get exposed as standalone functions that take the receiver as the first parameter or something
That's exactly what happens, yes.
t
Yeah, I would like to avoid that if possible, since it's not as ergonomic
To clarify, the
Container<out T>
thing was a simplified example vs what the class actually does
I was mainly trying to illustrate a simplified scenario to see if my understanding of
@UnsafeVariance
is correct
But I can provide more details if that'd help
d
@UnsafeVariance
is fine in your case. The reason Kotlin forbids just returning
Optional<T>
is code like this:
Copy code
interface MutableOptional<T> {
    fun mutate(newValue: T)
}

class Container<out T>(v: T) {
    
    private var vValue: T = v

    fun optional(): MutableOptional<@UnsafeVariance T> = object: MutableOptional<@UnsafeVariance T> {
        override fun mutate(newValue: @UnsafeVariance T) {
            vValue = newValue
        }
    }
    
    val v: T get() = vValue
}

fun main() {
    val c = Container<Int>(3)
    val c2: Container<Any?> = c
    val o: MutableOptional<Any?> = c2.optional()
    o.mutate("4.3")
    println(c.v + 10) // throws an exception
}
When it comes to just the variance,
Optional
and
MutableOptional
are the indistinguishable. Java's
Optional
could have been marked with
out
as well, which means that your users can't really do anything with it that would break the guarantees of your container.
t
So the reason it's safe for Java's
Optional<T>
is because the container cannot be mutated, right?
d
Yes.
t
Nice nice, thanks for the explanations/example 🙂