Is there any plan to improve the exception handlin...
# language-evolution
j
Is there any plan to improve the exception handling in the nearest future, i.e. enforcing or at least suggesting that there might be exceptions worth handling at compile-time? I know this topic has been discussed multiple times already, I’ve seen a proposal where someone suggested an approach similar to Swift’s, I’ve also seen an apparently still ongoing debate that started as a feature request for checked exceptions but which eventually has evolved to be a discussion on exception handling in Kotlin in general. I’m bringing this up again here because, well, I’m simply not happy with the compiler not warning about exceptions in any way. Don’t get me wrong, I’m not opting for checked exceptions here. I do agree that Java’s way is far from ideal, but I just also happen to think that what Kotlin does right now, allowing any exception to be left unhandled and potentially crash the application, isn’t good either. Especially that it comes from a language whose one of the core features is compile-time null safety which aims to help people avoid crashing their apps partially because of an oversight. And of course, I do understand that it’s a complex issue, many people have different opinions on it, probably some won’t even agree with what I’ve written above, but, regardless of these opinions, exceptions are part of the language and could use better support. Maybe there could be a compiler flag, similar to what we got for the explicit API mode, which would allow developers to tweak the compiler into helping them: • not to forget to annotate their throwing functions with
@Throws
• not to overlook functions annotated with
@Throws
and handle them properly?
e
how does that not make it equivalent to checked exceptions in the language?
anyhow I can see why you'd want this in some situations, but I think it has some serious ramifications on the type system. the types of lambdas, at least
() -> Unit
is a
@Throws(E::class) () -> Unit
but you can't allow vice versa, if you want to be able to track exceptions through lambda blocks (which you'd have to, for consistency with existing Kotlin standard library usage at least)
@Throws(E::class, F::class)
and
@Throws(F::class, E::class)
are equivalent
we don't have a way to represent
<T...> @Throws(T::class...)
, maybe with union types but that's a whole other challenge to get right
c
I think the better approach, rather than adjusting the language, is to adjust your thinking about the runtime safety of your program. Just like in Java, you eventually just need to assume any given variable can be null no matter how much you try to protect against it. In Kotlin you need to assume any given function could throw an exception, and it’s up to you to define how you are going to handle that case. Nulls make sense to check with the compiler as you actually can prevent variables from ever becoming null, because they start and end entirely within the Kotlin language (and when they don’t get get a “platform type”). Additionally, you as a programmer can generally recover easily from a null variable when one does arise. In contrast, exceptions commonly originate from outside of the Kotlin language. The biggest offender is obviously Java’s stdlib and Java libraries, but even beyond that, there is always the potential for something within the runtime environment itself or the OS to go horribly wrong and generate some error that gets bubbled up to your program. And with these kinds of errors, you typically can’t do anything clever with the exception other than log it, so it doesn’t really improve your code any to be consciously aware of it. Just assume that your code could throw an error, and make sure there’s a centralized error handler near the top of your application, and now you don’t need to worry about the vast majority of exceptions that get thrown.
And as another note, many people that propose these kinds of language changes typically come from a Java background, but it’s worth remembering that Kotlin has evolved far beyond just a “better Java”. Kotlin is a language and environment in its own right, and it’s not trying to copy Java or work exclusively to make Java developers happy. It’s a different language that offers a different way of thinking about your code, and you need to learn to do thinks the “Kotlin Way” rather than keep doing things the “Java Way” with different syntax
j
@ephemient
how does that not make it equivalent to checked exceptions in the language?
Fair point. I guess the difference would be that by default it would stay as it is and only if you really wanted or had to, would you turn it on so the compiler could warn you or abort with an error (again, similarly to how the explicit API mode works). So technically, you could say they’re unchecked unless you explicitly ask otherwise. Other than that, part of me just wanted to start a discussion on the topic and see what others have to say about it and what a better way than throw in an idea that can be dismantled and, hopefully, improved? 🙂
e
there’s certainly been some contentious discussion about this before, but you came at this earnestly and I tried to respond fairly 🙂 I think the most likely next step would be to try to work out https://youtrack.jetbrains.com/issue/KTIJ-10948 - that should be fairly non-controversial
due to what I wrote above, I think it’ll be a tough challenge to do anything beyond IDE advice, because
Copy code
fun f(block: () -> Unit) { block() }
fun g(block: () -> Unit) { callbacks += block }

fun main() {
    f { throw Exception() }
    g { throw Exception() }
}
assuming
f
and
g
are code in some external library, clearly calling
f
should tell you to add
@Throws(Exception::class)
to
main
and calling
g
shouldn't, but we can't actually analyze that accurately
(and as I see it, it's OK for IDE advice to be based on heuristics that are sometimes wrong, but compiler errors, not so much)
j
@Casey Brooks
I think the better approach, rather than adjusting the language, is to adjust your thinking about the runtime safety of your program [...]
I agree with what you’ve said there... mostly. The only thing I don’t agree with is the part where you said that “you have to assume”. Why should we assume? This is exactly the reason software usually doesn’t work as intended, because a developer assumed something incorrectly, whether it was an access to a null reference in Java, a property missing in a JavaScript object or an overlooked exception in Kotlin. I’ve always thought that the goal is to improve how we write code, leaving less and less space for human error and busting the quality of code or even the experience of writing it by reducing the amount of redundancy that needs to be added due to some uncertainties. Kotlin did great on many levels, but, disputably, exceptions seem to be left behind or maybe it took a step back with them by making the code depending on them more error-prone.
Just assume that your code could throw an error, and make sure there’s a centralized error handler near the top of your application, and now you don’t need to worry about the vast majority of exceptions that get thrown.
True, but I would argue that, depending on a framework you use or the purpose of your application, it could be impossible to have only one truly centralized handler and rather you’d find yourself scoping your error handling between different parts of your applications. The more places where you have to remember to put your handler to, the more likely it gets that eventually you miss one, exposing yourself to a threat of a crash. I haven’t put that much thought into this yet, but I’m also expecting that it can get even more interesting when we bring asynchronous code into this. In general, I’m not looking for a way to write code as people would in Java, I’m looking for a way of improving the compile-time safety of my Kotlin code. And as long as someone can throw an exception in a 3rd party library I’m using and the compiler won’t, at least, warn me about it, I can’t be sure if each line of my code is truly covered by my handlers.
@ephemient I guess an IDE check is better than nothing, I’ll take that 😄 I actually wasn’t expecting that any work on exception handling was underway, either on the language level or IDE, so it makes me happy in the end that it hasn’t been forgotten. Thank you 🙂
e
the "Kotlin way" has many pragmatic compromises, and I think this is one of them. the designers clearly wanted a language with ergonomic lambdas and a type system that interoperates with Java's, which means • if you have checked exceptions like Java, then ◦ if you have a single function type, then ▪︎ if it's non-throwing, then you're limited in what you can do inside of a lambda ▪︎ if it's throwing, then you're limited in where you can invoke a lambda ◦ if you have two function types, one for non-throwing and one throwing
Exception
, then ▪︎ you can't write Swift-like higher-order functions (
rethrows
) that are generic over both without further tradeoffs, and ▪︎ it doesn't tell you which exception types are rethrown, so Java interoperability is still bad ◦ if you have function types for non-throwing and for throwing a generic
T
, then ▪︎ you still have the problem of generic higher-order functions ▪︎ you can't write lambdas that throw multiple exception types without losing type information ◦ if you have N function types, one for every set of possible thrown exception types, then ▪︎ congratulations, you are trying to tackle union types on JVM super early on in a language's life, good luck with that • if you don't have checked exceptions, then ◦ you only need one function type, JVM doesn't even care about declaring checked exceptions, everything is much simpler so regardless of whether you think checked exceptions are a good idea or a bad idea, I think this was the only reasonable path to Kotlin 1.0. now that the language is more mature, there's a potential for the Kotlin team to revisit past decisions, but I'm not sure how anything will pan out (especially since compatibility back to 1.0-compiled code is guaranteed)
I actually wasn’t expecting that any work on exception handling was underway
the existence of a YouTrack issue doesn't mean there's any work under way, it just means somebody filed an issue
j
the existence of a YouTrack issue doesn’t mean there’s any work under way, it just means somebody filed an issue
oh, you’re right, I took a too quick look yesterday and must have felt under wrong impression
a
we found a way to have checked exceptions using only the features in the language, and will become part of Arrow 2.0 (you can see the code at https://github.com/arrow-kt/arrow/blob/arrow-2/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/Raise.kt). The idea is to use a (context) receiver to declare the possible exceptions that may be raised, and the compiler is smart enough to check everything fits
e
if you ignore Java then sure :)