Why is 'throws' not type safe in Swift?
Asked Answered
W

3

61

The biggest misunderstanding for me in Swift is the throws keyword. Consider the following piece of code:

func myUsefulFunction() throws

We cannot really understand what kind of error it will throw. The only thing we know is that it might throw some error. The only way to understand what the error might be is by looking at the documentation or checking the error at runtime.

But isn't this against Swift's nature? Swift has powerful generics and a type system to make the code expressive, yet it feels as if throws is exactly opposite because you cannot get anything about the error from looking at the function signature.

Why is that so? Or have I missed something important and mistook the concept?

Whortleberry answered 21/11, 2016 at 11:0 Comment(6)
Somewhat related: Can I restrict the type that a function throws in Swift?Damiendamietta
There also was a discussion on the Swift Evolution mailing list about this.Damiendamietta
@Damiendamietta I realize I was bit too brief in my thoughts above: I tried to first cover the simplified scenario where we only have functions that throws: in this case, we can naturally annotate the error type. What I stated in the following clause in my comment was based on the misconception that rethrows mustn't necessarily be thrown by arguments to the rethrowing functions, but from any throwing function used in its body (which, in itself, may just be re-throwing its propagated error). In case of the latter, I believe we'd need to investigate the call stack to see where the initial ...Gobi
... error propagated from, as we do not know from the function signature of such a rethrowing function which types it may rethrow (alternative, we would be very limited in how to use throwing functions in the body of a rethrows function). But again, this was all based on the misconception that error thrown from rethrows functions aren't necessarily thrown from its arguments.Gobi
@dfri I guess with rethrows - either your higher level function should throw the same error or should map to another kind of errorWhortleberry
@Whortleberry yeah, as I wrote, I mistakenly thought errors thrown in rethrows functions needn't necessarily be thrown from its arguments. Since only supplied argument throws, naturally typed error types of the provided (throwing) closures (typed in function signature) should be able to be included in such a scenario (which would directly bridge over to the error type of the rethrows function itself)Gobi
S
24

The choice is a deliberate design decision.

They did not want the situation where you don't need to declare exception throwing as in Objective-C, C++ and C# because that makes callers have to either assume all functions throw exceptions and include boilerplate to handle exceptions that might not happen, or to just ignore the possibility of exceptions. Neither of these are ideal and the second makes exceptions unusable except for the case when you want to terminate the program because you can't guarantee that every function in the call stack has correctly deallocated resources when the stack is unwound.

The other extreme is the idea you have advocated and that each type of exception thrown can be declared. Unfortunately, people seem to object to the consequence of this which is that you have large numbers of catch blocks so you can handle each type of exception. So, for instance, in Java, they will throw Exception reducing the situation to the same as we have in Swift or worse, they use unchecked exceptions so you can ignore the problem altogether. The GSON library is an example of the latter approach.

We chose to use unchecked exceptions to indicate a parsing failure. This is primarily done because usually the client can not recover from bad input, and hence forcing them to catch a checked exception results in sloppy code in the catch() block.

https://github.com/google/gson/blob/master/GsonDesignDocument.md

That is an egregiously bad decision. "Hi, you can't be trusted to do your own error handling, so your application should crash instead".

Personally, I think Swift gets the balance about right. You have to handle errors, but you don't have to write reams of catch statements to do it. If they went any further, people would find ways to subvert the mechanism.

The full rationale for the design decision is at https://github.com/apple/swift/blob/master/docs/ErrorHandlingRationale.rst

EDIT

There seems to be some people having problems with some of the things I have said. So here is an explanation.

There are two broad categories of reasons why a program might throw an exception.

  • unexpected conditions in the environment external to the program such as an IO error on a file or malformed data. These are errors that the application can usually handle, for example by reporting the error to the user and allowing them to choose a different course of action.
  • Errors in programming such as null pointer or array bound errors. The proper way to fix these is for the programmer to make a code change.

The second type of error should not, in general be caught, because they indicate a false assumption about the environment that could mean the program's data is corrupt. There my be no way to continue safely, so you have to abort.

The first type of error usually can be recovered, but in order to recover safely, every stack frame has to be unwound correctly which means that the function corresponding to each stack frame must be aware that the functions it calls may throw an exception and take steps to ensure that everything gets cleaned up consistently if an exception is thrown, with, for example, a finally block or equivalent. If the compiler doesn't provide support for telling the programmer they have forgotten to plan for exceptions, the programmer won't always plan for exceptions and will write code that leaks resources or leaves data in an inconsistent state.

The reason why the gson attitude is so appalling is because they are saying you can't recover from a parse error (actually, worse, they are telling you that you lack the skills to recover from a parse error). That is a ridiculous thing to assert, people attempt to parse invalid JSON files all the time. Is it a good thing that my program crashes if somebody selects an XML file by mistake? No isn't. It should report the problem and ask them to select a different file.

And the gson thing was, by the way, just an example of why using unchecked exceptions for errors you can recover from is bad. If I do want to recover from somebody selecting an XML file, I need to catch Java runtime exceptions, but which ones? Well I could look in the Gson docs to find out, assuming they are correct and up to date. If they had gone with checked exceptions, the API would tell me which exceptions to expect and the compiler would tell me if I don't handle them.

Spermatophyte answered 21/11, 2016 at 14:7 Comment(24)
The thing is, a very popular pattern in Swift is to use enumerations as error types. This allows us to define a single type of error that a function can throw, but has multiple cases that describe exactly what went wrong, with additional information with each case if necessary. This fact pretty much combats the argument of "you'll be forced to write out tons of catch blocks", as enforcing that a function throws a single enumeration type still allows you to just write a single catch block. Personally I think the added safety of allowing throws to be type-annotated far outweighs any drawbacks.Damiendamietta
@Damiendamietta But if your API throws a particular type and then internally chooses to use a library that throws another different type, you either have to change your API to throw both types (assuming you want to allow the error to propagate, introducing a new dependency for the caller or you wrap the new exception in a new case of your type. Either way, callers of your API have to recompile unless they have default cases in their catch or switch, both of which defeat the purpose of the typed exception.Spermatophyte
@Damiendamietta I'm not saying it's wrong to have typed errors, but experience shows that people will get bored of the overhead they introduce and subvert the mechanism, just as they have with Java.Spermatophyte
...because that makes callers have to either assume all functions throw exceptions and include boilerplate to handle exceptions that might not happen, or to just ignore the possibility of exceptions -- Neither of these assertions is true. Every properly-written C# program has a backstop try/catch handler somewhere near the top of the call stack that prevents the program from crashing if an unhandled exception occurs anywhere in the framework and logs the exception. The developer can then decide what to do about those unhandled exceptions that actually occur in practice.Botryoidal
As a c# programmer, I don't follow your second paragraph.Ppi
@RobertHarvey what you describe comes under the heading of ignoring the possibility of an exception.Spermatophyte
@Ppi in Java, for example all exceptions derive from a class called Exception but an API can declare which specific subclasses it can throw. You either need a catch block for each specific one or you need to throw all of them yourself or you catch/throw some superclass e.g. Exception itself. If you do the latter you are effectively where the Swift system is.Spermatophyte
@JeremyP: There is always the possibility of an exception being thrown, unless you're using something like TryParse(), perhaps even then. I don't see how providing a backstop exception handler could be categorized as ignoring that possibility.Botryoidal
quote paragraph: <<(... gson ...) "That is an egregiously bad decision. "Hi, you can't be trusted to do your own error handling, so your application should crash instead".>> It's actually the only sane decision, and shows quite some misunderstanding on your part, IMHO. More importantly, it seems quite contentious, and also doesn't add anything to the otherwise very good answer. I'd remove it. YMMV.Aronoff
@Spermatophyte you talk about java, but the paragraph in question doesn't mention java. Furthermore you seem to be under the impression that just because an exception could be thrown that is should be caught? If you dispensed with that mindset you might understand C#'s exception system more clearly.Ppi
That is an egregiously bad decision. That is entirely your opinion and it is most unwelcomed. You may disagree with the design choice, but, quite obviously, other people think it's a good idea to do that. Calling their work "egregiously bad" is not ok in my book, and very disrespectful.Libbie
Agreeing with those who say it's not "an egregiously bad decision" at all. Please educate yourself on the "fail fast" philosophy and its advantages for debugging and for preventing corruption.Friary
I think this answer espouses a deep misunderstanding of how exceptions are used in practice. The "two categories" way of thinking is a false dichotomy. For the "second type," the web application is a common counterexample. It should handle all exceptions because one request's failure should not deny service to all other requests. For the "first type," the compiler giving you an error that you didn't catch an exception by no means enforces that you closed your file, so it's just noise. It is far safer and even more efficient to use the convention that you always clean up your resources.Fayum
You're terribly wrong with conflating exception handling with resource cleanup in Java. First of all, because of GC, the vast majority of methods needs no cleanup at all. Those which do, use a finally block. In any case, knowing what exception occurred (and if any) is irrelevant for the cleanup. +++ You're equally wrong about crashing the program. For example, when my web request fails, it may be due to malformed input or due to my mistake. In both cases, nothing gets written in the DB and life goes on. The only difference is that in the second case, I have to fix it.Anoxia
@Anoxia Firstly, it is not just about resource clean up, it is also about data consistency. If a method expects to set a certain member of a certain object to a non null value, but an exception is thrown before it gets the chance, you need to be able to handle that issue. Secondly, not all resources are memory. You need to close files because relying on garbage collection to close them is sheer stupidity. And I agree you don't need to know what exceptions can be thrown but you do need to know that an exception can be thrown. That's why I think Swift more or less gets it right.Spermatophyte
@Anoxia Also if you think programmer errors require the same handling as data errors, you really do need to think again.Spermatophyte
"not all resources are memory" - sure, that's why I wrote "use a finally block". +++ "need to know that an exception" - no, just always use finally for cleanup. +++ I just can't imagine any non-trivial non-throwing method. +++ "programmer errors require the same handling as data errors" - indeed, they both require displaying a message, logging and rollback. Neither requires a special cleanup as the cleanup must happen anyway, even when the scope is scope is left normally. +++ Actually, programming errors leading to an exception are usually the benign ones...Anoxia
So you want people to always add a try ... finally ... block to every single function that calls another function and I agree you would have to without checked exceptions. However, this is unnecessary boilerplate in a language with checked exceptions. Also, I can imagine non trivial non throwing methods. I can also imagine trivial throwing methods.Spermatophyte
You seem to have forgotten that data errors can be recovered from as a rule whereas programming errors can't. If an exception is unchecked, you really have no option but to terminate the program because there is no way you can be sure its internal state is still consistent.Spermatophyte
@JeremyP: You'd be surprised. For many types of applications, programming errors in business logic only need to abort the individual business execution, not the entire process, provided there is a separately abstracted layer that properly isolates the business executions. So if you have a web-page, for example, you can swallow a NullPointerException from one request and display a 500 error, while still handling other requests, provided the internal state is scoped to the request.Cymbiform
Re: "So you want people to always add a try ... finally ... block to every single function that calls another function": The great majority of Java methods do not need try ... finally ..., because the finally block would be empty anyway. You need try ... finally ... when you acquire a resource (a file, a DB connection, etc.) and need to guarantee that you release it; but comparatively few methods acquire resources like that.Cymbiform
Obviously, in a language that doesn't allow code to catch exceptions, no sane person would design a file API where the absence of a file throws an exception, or a networking API where the failure to connect throws an exception. In such a language, these cases would be described through a result type. When exceptions are reserved for programming errors or environment failures (NRE, bounds violation, access violation, memory shortage, stack overflow, etc) there is nothing bad about fast exiting.Prototherian
@Spermatophyte You're wrong. In the 25k code lines of my current project, there are 50 finally blocks (most of them freeing a resource). Fewer than 1 in 20 methods. +++ "no option but to terminate" - the program is a web application which must not terminate except when told to. What gets terminated is the current request, which is pointless to process any further in case of any exception. Note that every non-trivial program is buggy. If it throws, inconsistencies may follow. If it doesn't, inconsistencies may happen, too. A runtime exception is just a sign of a bug, not a problem itself.Anoxia
@Spermatophyte "...you really have no option but to terminate the program because there is no way you can be sure its internal state is still consistent." I'm managing a SAAS backend right now, and we measure exceptions in the tens of thousands, hundreds of thousands and even millions of errors a day. We simply have some high level logging that recognizes something went wrong (with useful context) and keep on chugging. Not ideal, but pragmatic for now.Ppi
V
41

I was an early proponent of typed errors in Swift. This is how the Swift team convinced me I was wrong.

Strongly typed errors are fragile in ways that can lead to poor API evolution. If the API promises to throw only one of precisely 3 errors, then when a fourth error condition arises in a later release, I have a choice: I bury it somehow in the existing 3, or I force every caller to rewrite their error handling code to deal with it. Since it wasn't in the original 3, it probably isn't a very common condition, and this puts strong pressure on APIs not to expand their list of errors, particularly once a framework has extensive use over a long time (think: Foundation).

Of course with open enums, we can avoid that, but an open enum achieves none of the goals of a strongly typed error. It is basically an untyped error again because you still need a "default."

You might still say "at least I know where the error comes from with an open enum," but this tends to make things worse. Say I have a logging system and it tries to write and gets an IO error. What should it return? Swift doesn't have algebraic data types (I can't say () -> IOError | LoggingError), so I'd probably have to wrap IOError into LoggingError.IO(IOError) (which forces every layer to explicitly rewrap; you can't have rethrows very often). Even if it did have ADTs, do you really want IOError | MemoryError | LoggingError | UnexpectedError | ...? Once you have a few layers, I wind up with layer upon layer of wrapping of some underlying "root cause" that have to be painfully unwrapped to deal with.

And how are you going to deal with it? In the overwhelming majority of cases, what do catch blocks look like?

} catch {
    logError(error)
    return
}

It is extremely uncommon for Cocoa programs (i.e. "apps") to dig deeply into the exact root cause of the error and perform different operations based on each precise case. There might be one or two cases that have a recovery, and the rest are things you couldn't do anything about anyway. (This is a common issue in Java with checked exception that aren't just Exception; it's not like no one has gone down this path before. I like Yegor Bugayenko's arguments for checked exceptions in Java which basically argues as his preferred Java practice exactly the Swift solution.)

This is not to say that there aren't cases where strongly typed errors would be extremely useful. But there are two answers to this: first, you're free to implement strongly typed errors on your own with an enum and get pretty good compiler enforcement. Not perfect (you still need a default catch outside the switch statement, but not inside), but pretty good if you follow some conventions on your own.

Second, if this use case turns out to be important (and it might), it is not difficult to add strongly typed errors later for those cases without breaking the common cases that want fairly generic error handling. They would just add syntax:

func something() throws MyError { }

And callers would have to treat that as a strong type.

Last of all, for strongly typed errors to be of much use, Foundation would need to throw them since it is the largest producer of errors in the system. (How often do you really create an NSError from scratch compared to deal with one generated by Foundation?) That would be a massive overhaul of Foundation and very hard to keep compatible with existing code and ObjC. So typed errors would need to be absolutely fantastic at solving very common Cocoa problems to be worth considering as the default behavior. It couldn't be just a little nicer (let alone have the problems described above).

So none of this is to say that untyped errors are the 100% perfect solution to error handling in all cases. But these arguments convinced me that it was the right way to go in Swift today.

Ventura answered 21/11, 2016 at 14:50 Comment(13)
"it is not difficult to add strongly typed errors later for those cases without breaking the common cases that want fairly generic error handling" There are Java projects where I wish they would do that...Seat
I don't buy their opinion. Though SO is not a good platform for discussion... IMO, (1) Typed error might be bad for APIs, but very good for apps especially if I use error throwing as a flow control because I need predictable branching. (2) rethrow is bad. Higher level code usually cannot understand or handle lower level errors properly. Due to lack of context. All lower level errors need to be handled at mid-level and mid-level should produce a proper mid-level error for higher level code. Just rethrowing means just doing nothing for errors.Ankh
Even for APIs, I think providing a sort of classification using protocols or something would be better than nothing. I hope them to add error type check ASAP...Ankh
One of your arguments (maybe even your main argument) is that when an API adds error cases, it would break existing code and force the API consumer to deal with the new case. I think that it is actually a good thing. After updating the API, the compiler will tell me that I need to handle a new error case and will point me to the exact location where I need to handle it. Yes! I want that!Cornelius
@Cornelius will point me to the exact location where I need to handle it: yes, that's precisely what I thought too. But I guess the argument is that most developers don't have the bandwidth to deal with new errors, and would therefore simply get frustrated when their code needs to be rebuilt (they'd rather just let existing code "somehow" handle any new errors, even if not quite perfectly). This in turn means that the library developers (who don't want frustrated users) would end up avoiding adding new error types, which is a bad outcome.Mccord
@Cornelius Another point here is, it's easy to wrap strongly typed errors into a "common error type" to hide their types (e.g. AnyError). Though in many cases we do not want to deal with specific error type, but also in many cases, I want to define a sort of "my own common error protocol/type" to provide common way of handling errors. But by disallowing strongly typed errors, now it's simply impossible to express type requirements on any situation. Type-erased errors are useful, but it doesn't mean only type-erased errors should be allowed everywhere.Ankh
I think now Swift team thinking opposite when they have provided us error strong typed Combine in which I think is the main advantage over RxSwift.Silvie
@Silvie The Swift team did not write Combine; it's an Apple-specific framework. The Swift team developed Structured Concurrency (async/await), which is portable to all Swift platforms, part of the Swift GitHub repo, etc. IMO, Structured Concurrency will replace most (though not all) of the use cases for Combine. WWDC '21 didn't even have a session on Combine. It's cool tech, but I've always considered it transitionary, and I think my predictions are turning out right about it. The rule, IMO, is "good Swift throws errors." Combine is awkward to use with throws. It's core to Async/await.Ventura
@RobNapier is it portable on iOS 12?Silvie
No. If you're supporting older versions of iOS, you'll likely still use some Combine. Much like SwiftUI and UIKit. Both will have their uses for a long time, and for supporting old versions you can only use UIKit, but SwiftUI is clearly the future.Ventura
@RobNapier to bad that with asyn/await feature we have got error type-unsafe version of Combine.Silvie
I don't really agree with dismissing throwing of strongly-typed errors for it'd be easy to break public APIs reasons. Alamofire uses strongly-typed error with Combine github.com/Alamofire/Alamofire/blob/master/Documentation/… for instance and the async/await version works around it by returning a result instead of throwing github.com/Alamofire/Alamofire/blob/master/Documentation/….Subclavian
Small nit: Swift does have ADTs: enum and struct are what is called sum and product types, respectively, in the context ADTs. These are also the most common, and sometimes only, ADTs of a language's type system. What you're probably thinking about with the | syntax are (non-disjoint) union types, the way e.g. TypeScript or Scala have them.Johann
S
24

The choice is a deliberate design decision.

They did not want the situation where you don't need to declare exception throwing as in Objective-C, C++ and C# because that makes callers have to either assume all functions throw exceptions and include boilerplate to handle exceptions that might not happen, or to just ignore the possibility of exceptions. Neither of these are ideal and the second makes exceptions unusable except for the case when you want to terminate the program because you can't guarantee that every function in the call stack has correctly deallocated resources when the stack is unwound.

The other extreme is the idea you have advocated and that each type of exception thrown can be declared. Unfortunately, people seem to object to the consequence of this which is that you have large numbers of catch blocks so you can handle each type of exception. So, for instance, in Java, they will throw Exception reducing the situation to the same as we have in Swift or worse, they use unchecked exceptions so you can ignore the problem altogether. The GSON library is an example of the latter approach.

We chose to use unchecked exceptions to indicate a parsing failure. This is primarily done because usually the client can not recover from bad input, and hence forcing them to catch a checked exception results in sloppy code in the catch() block.

https://github.com/google/gson/blob/master/GsonDesignDocument.md

That is an egregiously bad decision. "Hi, you can't be trusted to do your own error handling, so your application should crash instead".

Personally, I think Swift gets the balance about right. You have to handle errors, but you don't have to write reams of catch statements to do it. If they went any further, people would find ways to subvert the mechanism.

The full rationale for the design decision is at https://github.com/apple/swift/blob/master/docs/ErrorHandlingRationale.rst

EDIT

There seems to be some people having problems with some of the things I have said. So here is an explanation.

There are two broad categories of reasons why a program might throw an exception.

  • unexpected conditions in the environment external to the program such as an IO error on a file or malformed data. These are errors that the application can usually handle, for example by reporting the error to the user and allowing them to choose a different course of action.
  • Errors in programming such as null pointer or array bound errors. The proper way to fix these is for the programmer to make a code change.

The second type of error should not, in general be caught, because they indicate a false assumption about the environment that could mean the program's data is corrupt. There my be no way to continue safely, so you have to abort.

The first type of error usually can be recovered, but in order to recover safely, every stack frame has to be unwound correctly which means that the function corresponding to each stack frame must be aware that the functions it calls may throw an exception and take steps to ensure that everything gets cleaned up consistently if an exception is thrown, with, for example, a finally block or equivalent. If the compiler doesn't provide support for telling the programmer they have forgotten to plan for exceptions, the programmer won't always plan for exceptions and will write code that leaks resources or leaves data in an inconsistent state.

The reason why the gson attitude is so appalling is because they are saying you can't recover from a parse error (actually, worse, they are telling you that you lack the skills to recover from a parse error). That is a ridiculous thing to assert, people attempt to parse invalid JSON files all the time. Is it a good thing that my program crashes if somebody selects an XML file by mistake? No isn't. It should report the problem and ask them to select a different file.

And the gson thing was, by the way, just an example of why using unchecked exceptions for errors you can recover from is bad. If I do want to recover from somebody selecting an XML file, I need to catch Java runtime exceptions, but which ones? Well I could look in the Gson docs to find out, assuming they are correct and up to date. If they had gone with checked exceptions, the API would tell me which exceptions to expect and the compiler would tell me if I don't handle them.

Spermatophyte answered 21/11, 2016 at 14:7 Comment(24)
The thing is, a very popular pattern in Swift is to use enumerations as error types. This allows us to define a single type of error that a function can throw, but has multiple cases that describe exactly what went wrong, with additional information with each case if necessary. This fact pretty much combats the argument of "you'll be forced to write out tons of catch blocks", as enforcing that a function throws a single enumeration type still allows you to just write a single catch block. Personally I think the added safety of allowing throws to be type-annotated far outweighs any drawbacks.Damiendamietta
@Damiendamietta But if your API throws a particular type and then internally chooses to use a library that throws another different type, you either have to change your API to throw both types (assuming you want to allow the error to propagate, introducing a new dependency for the caller or you wrap the new exception in a new case of your type. Either way, callers of your API have to recompile unless they have default cases in their catch or switch, both of which defeat the purpose of the typed exception.Spermatophyte
@Damiendamietta I'm not saying it's wrong to have typed errors, but experience shows that people will get bored of the overhead they introduce and subvert the mechanism, just as they have with Java.Spermatophyte
...because that makes callers have to either assume all functions throw exceptions and include boilerplate to handle exceptions that might not happen, or to just ignore the possibility of exceptions -- Neither of these assertions is true. Every properly-written C# program has a backstop try/catch handler somewhere near the top of the call stack that prevents the program from crashing if an unhandled exception occurs anywhere in the framework and logs the exception. The developer can then decide what to do about those unhandled exceptions that actually occur in practice.Botryoidal
As a c# programmer, I don't follow your second paragraph.Ppi
@RobertHarvey what you describe comes under the heading of ignoring the possibility of an exception.Spermatophyte
@Ppi in Java, for example all exceptions derive from a class called Exception but an API can declare which specific subclasses it can throw. You either need a catch block for each specific one or you need to throw all of them yourself or you catch/throw some superclass e.g. Exception itself. If you do the latter you are effectively where the Swift system is.Spermatophyte
@JeremyP: There is always the possibility of an exception being thrown, unless you're using something like TryParse(), perhaps even then. I don't see how providing a backstop exception handler could be categorized as ignoring that possibility.Botryoidal
quote paragraph: <<(... gson ...) "That is an egregiously bad decision. "Hi, you can't be trusted to do your own error handling, so your application should crash instead".>> It's actually the only sane decision, and shows quite some misunderstanding on your part, IMHO. More importantly, it seems quite contentious, and also doesn't add anything to the otherwise very good answer. I'd remove it. YMMV.Aronoff
@Spermatophyte you talk about java, but the paragraph in question doesn't mention java. Furthermore you seem to be under the impression that just because an exception could be thrown that is should be caught? If you dispensed with that mindset you might understand C#'s exception system more clearly.Ppi
That is an egregiously bad decision. That is entirely your opinion and it is most unwelcomed. You may disagree with the design choice, but, quite obviously, other people think it's a good idea to do that. Calling their work "egregiously bad" is not ok in my book, and very disrespectful.Libbie
Agreeing with those who say it's not "an egregiously bad decision" at all. Please educate yourself on the "fail fast" philosophy and its advantages for debugging and for preventing corruption.Friary
I think this answer espouses a deep misunderstanding of how exceptions are used in practice. The "two categories" way of thinking is a false dichotomy. For the "second type," the web application is a common counterexample. It should handle all exceptions because one request's failure should not deny service to all other requests. For the "first type," the compiler giving you an error that you didn't catch an exception by no means enforces that you closed your file, so it's just noise. It is far safer and even more efficient to use the convention that you always clean up your resources.Fayum
You're terribly wrong with conflating exception handling with resource cleanup in Java. First of all, because of GC, the vast majority of methods needs no cleanup at all. Those which do, use a finally block. In any case, knowing what exception occurred (and if any) is irrelevant for the cleanup. +++ You're equally wrong about crashing the program. For example, when my web request fails, it may be due to malformed input or due to my mistake. In both cases, nothing gets written in the DB and life goes on. The only difference is that in the second case, I have to fix it.Anoxia
@Anoxia Firstly, it is not just about resource clean up, it is also about data consistency. If a method expects to set a certain member of a certain object to a non null value, but an exception is thrown before it gets the chance, you need to be able to handle that issue. Secondly, not all resources are memory. You need to close files because relying on garbage collection to close them is sheer stupidity. And I agree you don't need to know what exceptions can be thrown but you do need to know that an exception can be thrown. That's why I think Swift more or less gets it right.Spermatophyte
@Anoxia Also if you think programmer errors require the same handling as data errors, you really do need to think again.Spermatophyte
"not all resources are memory" - sure, that's why I wrote "use a finally block". +++ "need to know that an exception" - no, just always use finally for cleanup. +++ I just can't imagine any non-trivial non-throwing method. +++ "programmer errors require the same handling as data errors" - indeed, they both require displaying a message, logging and rollback. Neither requires a special cleanup as the cleanup must happen anyway, even when the scope is scope is left normally. +++ Actually, programming errors leading to an exception are usually the benign ones...Anoxia
So you want people to always add a try ... finally ... block to every single function that calls another function and I agree you would have to without checked exceptions. However, this is unnecessary boilerplate in a language with checked exceptions. Also, I can imagine non trivial non throwing methods. I can also imagine trivial throwing methods.Spermatophyte
You seem to have forgotten that data errors can be recovered from as a rule whereas programming errors can't. If an exception is unchecked, you really have no option but to terminate the program because there is no way you can be sure its internal state is still consistent.Spermatophyte
@JeremyP: You'd be surprised. For many types of applications, programming errors in business logic only need to abort the individual business execution, not the entire process, provided there is a separately abstracted layer that properly isolates the business executions. So if you have a web-page, for example, you can swallow a NullPointerException from one request and display a 500 error, while still handling other requests, provided the internal state is scoped to the request.Cymbiform
Re: "So you want people to always add a try ... finally ... block to every single function that calls another function": The great majority of Java methods do not need try ... finally ..., because the finally block would be empty anyway. You need try ... finally ... when you acquire a resource (a file, a DB connection, etc.) and need to guarantee that you release it; but comparatively few methods acquire resources like that.Cymbiform
Obviously, in a language that doesn't allow code to catch exceptions, no sane person would design a file API where the absence of a file throws an exception, or a networking API where the failure to connect throws an exception. In such a language, these cases would be described through a result type. When exceptions are reserved for programming errors or environment failures (NRE, bounds violation, access violation, memory shortage, stack overflow, etc) there is nothing bad about fast exiting.Prototherian
@Spermatophyte You're wrong. In the 25k code lines of my current project, there are 50 finally blocks (most of them freeing a resource). Fewer than 1 in 20 methods. +++ "no option but to terminate" - the program is a web application which must not terminate except when told to. What gets terminated is the current request, which is pointless to process any further in case of any exception. Note that every non-trivial program is buggy. If it throws, inconsistencies may follow. If it doesn't, inconsistencies may happen, too. A runtime exception is just a sign of a bug, not a problem itself.Anoxia
@Spermatophyte "...you really have no option but to terminate the program because there is no way you can be sure its internal state is still consistent." I'm managing a SAAS backend right now, and we measure exceptions in the tens of thousands, hundreds of thousands and even millions of errors a day. We simply have some high level logging that recognizes something went wrong (with useful context) and keep on chugging. Not ideal, but pragmatic for now.Ppi
F
0

💡Swift Typed Throws - Coming Soon!

It is going to be type safe in the near future. As you can see on Swift's GitHub, it is now merged on the main branch, behind the experimental feature flag TypedThrows (upcoming FullTypedThrows).

So soon, you can define a function that only throws CatError like:

func callCat() throws(CatError) -> Cat { ... }

and catch it later like:

do throws(CatError) {
  try callCat()
} catch let myError {
   // myError is of type CatError
}

🌐 Here is full detailed information about Typed Throws in Swift.

Flummox answered 31/12, 2023 at 12:24 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.