Let it crash

In Kotlin, it is not necessary to catch exception. it will crash the app if not handled.
In Swift, it is must to catch exceptions.

In theory, it is good. The application should catch possible errors and handle gracefully.
In practical, it encourages developers to just add try catch everywhere.
The production application might go to undefined state, the developers might not know about it, because they might not have added error logs.

Example, say userId is a global value, it is declared as nullable value, it is used in a place where it shouldn't be null at all.
In Kotlin, we can add assertion easily (userId!!), if it was null during that time, the app will crash, crash alert system will alert the developer, developer will fix the issue and make the application stable.

In Swift, usually the developers will use empty string as fallback value or return from the flow.

  • The application will go to undefined state. For example, it might hit an API call with empty userId but it was supposed to not
  • The developer will not know about this error case at all by default. The developer need to explicitly log the failure case as non fatal error, configure proper alert system and fix the issue. it never happens

My suggestion is, why not got back to the "let it crash" behaviour?
if needed, the developer shall catch the exception. but not by default.

i understand there are cases which needs to caught always.
File handling, network operations. Like checked exception in Java.
But most of the cases, allow the application to crash easily

For this case Swift hast the postfix forced-unwrap operator ! that will crash if a value is nil e.g:

let a: String? = nil
print(a!) // crash

Similarly Swift also has the force-try operator try! that will crash if an error is thrown. In addition there is also the force cast operator as!.

5 Likes

The two languages are very similar in this regard, there's ?: "Elvis" operator that matches Swift's ??, and !! matching Swift's !


The difference between Kotlin's !! and Swift's ! is that the first is catchable by the standard try/catch machinery, which is impossible with Swift's do/try/catch machinery without a seriously breaking change of making ! throwing (which is a no go as it would invalidate pretty much all Swift code written in the last 9 years) or until some new type of "unchecked" exceptions are introduced (which I don't see coming). It is possible to catch ! unwraps using signals see the relevant thread although I'd use it only as a last resort approach.

3 Likes

A bit off topic, but 'let it crash' philosophy doesn't mean system should stop working.

First of all it is good do unserstantf what do you mean by "exception"?

If you mean throwable functions and do {} catch {} construction then it is not an exception, it is about control flow.
func foo() throws -> String
is almost the same as
func foo() -> Result<String , Error>
You can convert one function to another.

The analogue of exceptions in Java / Kotlin is fatalError(), preconditionFailure() an NSException() – all of them will crash the app.

Rising of exception is a heavy operation. Swift throwing is lightweight like normal return.

Second thing is that let it crash approach is used in Erlang programming language, were crashes don't stop programm execution. When some actor is crashed, it is restarted by supervisor while programm continue execution.

As for Swift, you have two options for global userId case:

  • implicitly unwrapped optional: let userID: UUID!
  • optional let userID: UUID? with force unwrapping: let userID = userID!
    In both cases you will have a crash if value is nil.

Note that such approaches are discouraged and don't match with Swift idioms. Swift is different language, though very similar Kotlin syntactically in many cases. In Swift there are mechanisms for proper error handling instead of crashing which are preferred.
Fallback values are not always good choice, and empty strings leads to another problems as you mentioned. Global shared mutable state is a bad pattern in general. May be you have provided simplified or synthetic example, but very often there are other options instead of global variables.

2 Likes

Processes to be exact, but yes. And keep in mind they are lightweight and there could be like hundred thousands processes. So some can be crashed and restored while others are doing stuff => system always work.
So to be able to let it crash you need tools for that.

And agree here. Actually you can easily land into undefined state by just changing this id accidentally from many places—assert and crash of course will help, but then it will crash in random places.

Also not sure about example, but for handling this situation—if you think about optional in Swift is just an enum and could be pattern matched, something like:

enum Optional<V> {
   case some(V)
   case none
}

let userId: Optional<UUID> = .none //  or .some(UUID()) <- you can actually write like that in Swift

switch userId {
  case .some(let id): // do stuff
  case .none: // do another stuff
}

:top: which means there is no undefined state, but rather just two states, something like logged in/out state, idk. And you build app accordingly by passing this id rather accessing everywhere from one place.

Actually lately redefined and removed lots of optionals in our codebase at work and it just helped with understanding concrete number of states we have.

1 Like