Tired of messy try-catch blocks? Say hello to runC...
# feed
e
Tired of messy try-catch blocks? Say hello to runCatching! 🎉 runCatching offers a cleaner, more structured way to handle exceptions! Check out Kotlin Tips and Tricks You May Not Know: #7 https://medium.com/@elenavanengelen/kotlin-tips-and-tricks-you-may-not-know-7-goodbye-try-catch-hello-trycatching-7135cb382609
👎 6
👎🏻 1
l
So this is basically an attempt to retrofit checked exceptions back into Kotlin?
Don't take me wrong, I'm certainly a fan of well-designed API's with checked exceptions.
Anyway, shouldn't
Result
have a second type parameter for the exeception type that may be thrown?
e
runCatching is a built-in function in Kotlin designed to wrap computations and capture exceptions, returning a Result<T>. It doesn't "retrofit" checked exceptions, as Kotlin exceptions remain unchecked. Instead, it provides a structured way to handle failures while keeping the result type explicit, making error handling more predictable and composable.
h
It also catches CancellationException so it does not work well with coroutines.
👍 2
2
j
@loke the built-in
kotlin.Result
doesn't support specifying the error type, it's always
Throwable
. Keep in mind that the built-in one breaks structured concurrency, if you use it with coroutines (because it swallows `CancellationException`s). I wrote down some examples of that here, but the main recommendation I have is to use michaelbull/kotlin-result or the Arrow library's
Either
type instead.
b
You also cannot use it in any API that should be consumable from swift, since it is a value class
o
The article is wrong on several levels and it makes me mad. It was just generated by an LLM, wasn't it? Your
finally
replacement using
also
is skipped when you
return
from the error case instead of rethrowing. That's a footgun. The standard
finally
doesn't exhibit such error-prone behavior. In "Handling multiple exception types" you chose a nonsensical example since the two exceptions already form a hierarchy. You can just catch
IOException
and match the type like you do in the
runCatching
example, making the entire section moot. In "Handling nested exceptions", again, a bad example was chosen. The
try
example is bad code that wouldn't typically be written in the first place. Consider: your example:
Copy code
fun processFile(path: String): ProcessedData {
    return try {
        val content = File(path).readText()
        try {
            val json = parseJson(content)
            try {
                return processData(json)
            } catch (e: Exception) {
                logger.error(e) { "Failed to process data" }
                throw e
            }
        } catch (e: Exception) {
            logger.error(e) { "Failed to parse JSON" }
            throw e
        }
    } catch (e: Exception) {
        logger.error(e) { "Failed to read file: $path" }
        throw e
    }
}
How it would be written in the real world:
Copy code
fun processFile(path: String): ProcessedData {
    val content = try {
        File(path).readText()
    } catch (e: Exception) {
        logger.error(e) { "Failed to read file: $path" }
        throw e
    }

    val json = try {
        parseJson(content)
    } catch (e: Exception) {
        logger.error(e) { "Failed to parse JSON" }
        throw e
    }

    return try {
        processData(json)
    } catch (e: Exception) {
        logger.error(e) { "Failed to process data" }
        throw e
    }
}
Nobody programs like your examples and if they do, they don't pass code review. We don't nest block like this, regardless of whether it's
try/catch
or
if/else
. In "Falling Back to a Default Value", again, bad example. Why would you ever catch Throwable when trying to parse an integer? And why wouldn't you use the stdlib
String.toIntOrNull()
... in an article about the stdlib? The article tries to normalize blanket catching of Throwables for no good reason, which is not a good idea unless you have one of those 1:10_000 cases on the boundary of your system. As it stands, this is slop that reduces the signal/noise ratio, i.e. it is just noise.
lol 2
l
Yeah, if you run a static code analyser as part of your production chain, you should automatically reject any code that catches
Throwable
. The cases when that is the right thing to do are very few (if they even exist).
2
b
For the record: I think promoting an Either-Type on a conceptual level, and outlining what the stdlib does is a good thing. Also re-hashing it every once in a while is a good thing to create awareness among those now to Kotlin. I just think, full disclosure is in order: It will break structured concurrency and it cannot be used in any API that exports to swift, because value classes are erased. I do think it is a general issue though, that much is promoted that works JVM-only without ant disclaimers that the presented patterns/frameworks/libraries will not work using KMP or have limitations.
l
Also, log-then-throw is an antipattern. Either you log the error and deal with it, or you throw it upwards. You should rarely do both.
👍 3
@Bernd Prünster Value classes are erased in the JVM as well. I have code that takes advantage of this.
Assuming you use
@JvmInline
b
@loke true, but there you have little interop issues, because you can sill use kotlin
l
True, but there is one Java interop issue. I'd like to see the Kotlin compiler generate Java methods that can be used to access the functionality of methods that use inline classes. As it stands, these methods can't be called at all from Java since they're not visible from there.
o
> Value classes are erased in the JVM as well. I have code that takes advantage of this. Funny, I stopped using them for that same reason -- they are erased in JPA and so they break attribute converters Hibernate queries 😅 Though I'd much rather stop using JPA instead.
l
I use it in my bignum/rational library. It uses
expect
to wrap platform functionality, so the implementation looks like this:
Copy code
expect value class BigInt(val impl: Any)
This means that the actual class being used is going to be
java.math.BigInteger
in Java, and a special class wrapping a GMP object in native, etc. This avoids an extra level of indirection, that is pretty important since I can have arrays of billions of these.
e
Thanks for the feedback! I see areas where the article can be improved, including adding a section on coroutines and refining some examples. I’ll update the article soon to address these points.
b
@loke I never though of that approach! But you still can't really use it for either types, can you? What we did is have two helpers that behave like `runCatching`: one uses a plain
Result
to not pay the instantiation overhead and the other returns a non-value class. The latter (which also provides a mapper to
Result
) we use for APIs that need to be consumable from Swift. Neither breaks structured concurrency as we copied the logic from Arrow to filter exceptions.
l
@Bernd Prünster ah, you mean to avoid the overhead of a wrapper class to hold the either? Well, there are some things you could do, but it'll be a bit hacky: You'd create a value class that wraps an
Any
, and then the methods on it would have to use
is
to determine what type of object you have, so that it can do the right then. Then the
.value()
method would just be a type cast of the wrapped value. But yes, hacky, and certainly something you'd only do if it's really important to avoid the overhead.
I do a bunch of stuff like that in my project, but it's a programming language interpreter, so I need to take whatever measures I need in order to minimise overhead. Here's the project if you're curious: https://codeberg.org/loke/array
b
I just don't think this is possible in a generic manner without hacks. Still, I'll keep your considerations in mind. Maybe we'll incorporate some ideas in KmmResult, for specific cases that map well to platform types. Thanks!
e
@Bernd Prünster @loke @okarm @hfhbd @Jacob Ras I have updated the article to add coroutines and caution about catching throwable, removed also (good catch), also briefly mentioned arrow lib and added some refs. used one of your try catch examples as alternative Before. I did not replace some examples as they only serve as simplified examples of common scenarios. please shoot again! 🎯
b
On a quick glance, this still feels incomplete. While the note on needing to manually handle
CancellationException
does remedy breaking structured concurrency, having to jump through such hoops speaks against
runCatching
. More importantly, there are other Exceptions on the JVM that should never be caught, because when they do get thrown, all bets are off and you would be wise to just terminate the process. See the Arrow sources for a list. The root of all evil here, though, is not in your article (even in its original version) or whatever coding standards may or may not be violated by the examples, or
Result
being a value class which makes it unusable for APIs that should be consumable from Swift (which will still be a requirement for mobile developers). The actual issue here is JetBrains having introduced
runCatching
as a public-facing part of stdlib and actively advertising it in the past (do they still?). This discussion here is just a further indicator for how problematic the semantics of runCatching are, which is why it is verboten throughout our codebases (and we only use our own solution, because we don't want every project to depend on arrow). I almost certainly am not impartial, since I am the main author and maintainer of our drop-in replacement for the
Result
and
runCatching
tandem – but the simple fact that there's over a dozen of such replacements out there speaks for itself. I'm happy to discuss this issue further and I am curious about a couple of things, but this thread is probably not the right place. Edit: full disclosure - we used
runCatching
quite a lot and only by dumb luck avoided real issues. Only later did we find out how problematic
runCatching
really is.
1
s
I don’t need AI generated articles at all. That has absolutely no value to me. Everyone can ask ChatGPT on their own. Write the stuff yourself (and maybe only let revise your wording by ChatGPT).
2
e
@Stefan Oltmann I completely understand your point! Just to clarify, this article is fully written by me—it's not AI-generated. I occasionally use AI to refine wording or improve clarity, but the content, structure, and ideas are all my own. Thanks for your input!
👍 1
@Bernd Prünster I have updated the caution sections to also include extra attention to catching Throwable