Casting operator "as" as a method with type checking

I don’t really like the syntax of casting in Kotlin since it doesn’t play nice with chaining methods or require me with use “let”

someClass.getObject().let {it as SomeObject }

So I’ve created a method to replace this:

inline fun <reified V : Any> Any.asOrFail(): V = this as V

So now the code look something like that:

someClass.getObject().asOrFail<SomeObject>()

But the problem is that this function doesn’t respect type boundary since it has no information on the type of “this”.
I was trying to do something like this instead:

inline fun <T : Any, reified V : Any> T.asOrFail(): V = this as V

But the problem is that now I have to specify both types when calling the method which is really verbose since the “this” type is already known to the compiler:

someClass.getObject().asOrFail<BaseObject, SomeObject>()

Is there any alternative to this?

Can you expand on what you mean by “doesn’t respect type boundary”?

For me this fun works:

inline fun <reified T> Any.asOrFail(): T = this as T

fun main() {
	val x= ""
	println(x.asOrFail<Int>().dec())
}

=>

Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
	at TestKt.main(test.kt:10)
	at TestKt.main(test.kt)

And:

fun main() {
	val x= 12
	println(x.asOrFail<Int>().dec())
}

=>

11

So I don’t understand your problem!

I guess OP meant to allow casting to subtypes only, so this doesn’t compile: "hello".asOrFail<Int>(). Nice to have, although even the as operator doesn’t do this. It only generates a warning that the code always fails.

Just to specify exactly what I mean, If I have 2 unrelated classes (hierarchy wise): A and B and I try to case A to B:

class A
class B
fun castToA(a: A) = a as? B

I’ll get a compiler warning that the result of this cast is always null.
If I use my asOrFail method I don’t get this warning which makes this version unsafe.

I also tried this version:

inline fun <T: Any, reified V : T> T.asOrNull(c: Class<V>): V? = this as? V

But for some reason I can pass whatever class I want as an argument even if V doesn’t extends T

This is because T is inferred to Any in order to allow V to subtype it. This is just how generic functions work - they always try to infer types that will meet all requirements.

Kotlin has an internal feature which allows to “lock” parameter types to exactly provided types. We need to annotate the type with @kotlin.internal.Exact which is, well, internal:

@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
inline fun <T: Any, reified V : T> @kotlin.internal.Exact T.asOrNull(c: Class<V>): V? = this as? V

Of course, do it at your own risk, it may stop working in future versions of Kotlin.

1 Like

Thanks @broot ! That’s what I was looking for, just wish it was with a nicer syntax (without the need to pass an argument).
Do you know why is @kotlin.internal.Exact is internal? seems quite useful for some cases.