I think you may be on to something, but the idea of allowing unsafe smart-casts is not the solution. I’ll explain what I mean with unsafe smart-casts then suggest another idea.
Unsafe smart-casts
The request for unsafe smart-casts is similar to the request to drop the !!
requirement when calling a null.
Both of these requests are based on a few ideas:
- The compiler and the programer know the operation is unsafe
- The programer somehow knows the unsafe scenario will not occur
- The compiler should be weaker (not enforcing type safety or null safety in this specific case)
- All workaround options are not good enough (because they add boilerplate)
- The issue is very common
We’ve talked about points #1 and #2 a good amount and I think everyone agrees there are possible cases where both of those are true.
For points #3, #4, and #5, here’s my opinions:
- The compiler should be weaker (not enforcing type safety or null safety)
This is a valid request. There are plenty of benefits to a weaker compiler that does not enforce safety. Groovy follows this mentality where Kotlin follows a stricter approach. Although this might look like a solution to dealing with non-final variables (to allow unsafe smart-casts) and a solution to the rampant use of !!
(by not requiring !!
), there’s a better solution.
- All workaround options are not good enough (because they add boilerplate)
The workarounds posted I would not consider “workarounds” but instead solutions. They’re “solutions” because not only do they resolve your problem, they improve your code by making it safe. Just like how the solution to rampant use of !!
everywhere is to change the code to handle nulls properly, the solution to dealing with non-final variables is to change the code to capture them or use final variables. A separate issue resulting from these solutions may be the boilerplate, but that can be resolved other ways.
- This issue is very common and needs to be dealt a lot
Based on my experience, it’s rare to deal with a huge amount of non-final fields where I wouldn’t want to capture them as finals. Just like in Java, a great majority of your fields should be final. This is not a case of “only in a functional language is it possible”. Of course you could still be stuck in a project where mutable state is prevalent and you aren’t in a position to fix it. This issue isn’t as common as this topic would suggest.
Another Idea
Maybe what you really want is an easier way to capture local variables?
Normally, you would capture variables like this:
// Capturing in a local final variable
val x = this.x ?: "Hello World"
val x = this.x ?: error("Something went wrong")
val x = this.x!!
This would result in some boilerplate when there are multiple non-final variables that need to be captured.
val a = this.a ?: error("")
val b = this.b ?: error("")
val c = this.c ?: error("")
val d = this.d ?: error("")
...
Would adding multi-assignment fix the issue for you? This would improve your code by properly capturing the variables and may solve your issue of the difficulty of capture.
val (a, b, c, d) = (this.a, this.b, this.c, this.d)
Side note
Smart casts already work for local non-final variables
fun main(args: Array<String>) {
someMethod()
}
//sampleStart
fun someMethod() {
var localVar: String? = getStringOrNull()
if (localVar != null) {
// localVar is smartcast to type String here
println(localVar.length)
}
}
//sampleEnd
fun getStringOrNull() : String? {
return "StringOrNull"
}