Tangent: I love Swift's new typed throws! One frustration with Python is you can't--just by looking at function signature--tell that it raises exceptions. With Swift (and afaik, Java) you would write it in the function definition, but still, you wouldn't know what _type_ of exception to handle. Now that problem is solved!
More generally though, I wish we could avoid having exceptions to begin with. What's the reason behind their prevalence in almost every language?
In Java, typed exceptions are now considered an antipattern, because it caused problems in multiple parts of the language such as in lambdas. Results from rust tend to be more composable.
Do you know of any blogs or writing on this that I could read?
Naively, untyped exceptions in Swift always felt like an obvious type system loophole that we should be closing as soon as possible.
I trust that the "typed exceptions are an antipattern" people know what they're talking about, but I really don't understand the reasoning behind that position.
I’ve heard that claim many times before. I’m a daily Java developer, and can provide a little perspective on what Java is like. Though I don’t know the exact specifics of the claim.
In Java a given method either doesn’t throw any exceptions, or only throws the named ones and their children. So nothing, or maybe just InvailArgumentException (and its descendants).
However there’s a lot of different kinds of exceptions. So very quickly you tend to end up with one of two things. Functions have a big list of possible exceptions (an A, or B, or C, or F, or G) or the far more common in company code: throws Exception. Don’t bother to list stuff, just say “I throw stuff”.
Except, that’s all a lie. Because every function implicitly can throw RuntimeException and its children. Things like OutOfMemory or StackOverflow.
So basically all code everywhere realistically has to be aware of exceptions. A lot of people get lazy and don’t bother to list the actual kinds of exceptions they throw or pick one that’s a runtime exception so they don’t have to add the ‘throws’ to the signature to every single method.
And how do you solve calling a method with a checked exception? Well the right thing to do is use a try block, but so often people just add it to their own signature and pass the buck.
So a lot of the benefits just sort of get lost.
Swift now has three options: doesn’t throw, throws something, throws of type X. You can’t have multiple types. And you can’t pretend the exception doesn’t exist and just let it bubble up, you have to put a ‘try’ on the statement line to do that.
As someone who works in both languages Swift’s approach does seem like an improvement. I’m not sure how big.
But just in Swift, being able to explicitly state the one type of the exception that may be thrown seems like a very nice add for type safety and reducing boilerplate checks.
Minor nitpick: OutOfMemoryError and StackOverflowError are subclasses of Error, not Exception. It is an anti-pattern to catch any Error subclasses since these are usually JVM-internal exceptions. Programs are firmly in undefined behavior territory when they e.g. catch an OutOfMemoryError and do nothing with it. This is why catching Throwable is considered a bad idea compared to Exception. In the majority of cases, there is no reasonable recovery from a thrown Error, unlike a thrown Exception.
With that said, it is true RuntimeException and its subclasses are unchecked exceptions. Thankfully, those exceptions tend to be things like IllegalArgumentException, NullPointerException, etc., which are still pretty serious but not as bad as the two examples given.
Ah that’s right. I’m used to RuntimeException since that’s what people subclass to get out of the checked exception mess. The exact hierarchy around rare things like OutOfMemory just isn’t something that ever comes up in day to day work so it’s easy to forget.
The ThreadDeath Error in particular is interesting because it is catchable but not "handle-able" - if you do not rethrow ThreadDeath in your exception handler, the JVM will do so for you.
Despite Thread.stop() being deprecated, it is still used in things like application servers to deal with user code which is not completing in a specified timeframe.
> Except, that’s all a lie. Because every function implicitly can throw RuntimeException and its children. Things like OutOfMemory or StackOverflow.
> So basically all code everywhere realistically has to be aware of exceptions. A lot of people get lazy and don’t bother to list the actual kinds of exceptions they throw or pick one that’s a runtime exception so they don’t have to add the ‘throws’ to the signature to every single method.
Not just that, there are features like lambdas where the core language features were not designed with exceptions in mind. There are third party libraries which don't have allowances for proper failure cases. So you will have code adapted to wrap their exceptions into runtime exceptions for interoperability.
Of course, there's no guarantee the code which did not provide an interface which allowed for exceptions will not have unexpected side-effects when runtime exceptions or errors are being thrown.
> However there’s a lot of different kinds of exceptions. So very quickly you tend to end up with one of two things. Functions have a big list of possible exceptions (an A, or B, or C, or F, or G) or the far more common in company code: throws Exception. Don’t bother to list stuff, just say “I throw stuff”.
Ideally, someone creating an API thinks about how the others utilizing code will accomplish recovery or graceful failures, and craft an API to account for this. Unfortunately, the errors are often handed up, which really only accounts for broad failure behavior and corresponding cleanup (such as failing the corresponding HTTP request with a generic 500 error).
The heavy use of adapter patterns in Java means that the errors being handed up may be specific to the configuration, such as failures coming from the network layer of a database backend. It is crucial for the adapter interfaces to think about failure scenarios and recovery, but it unfortunately doesn't happen often very often.
So in most cases, Java should just throw Exception, because the only way a developer can really attempt recovery is to break encapsulation and operate based on knowledge of the inner workings of the object they were handed.
It's not a type system loophole in the sense that the type system is supposed to contain subtyping as a feature. In Java you could be unhelpful and pass every argument as type Object. (Of course the language allows you to cast the type afterwards.) That's not the fault of the type system it's the people using it. Do you hear people proposing to ban the use of Object in function arguments? No? Because it's a style issue not a type system issue.
You’re right but I think that’s pretty self-defeating so people wouldn’t really do it.
But there’s a pretty good argument that the way exceptions are implemented in Java makes the easiest thing to do simply be to start tacking “throws Exception“ on the end of tons of methods once you start to have trouble. Everywhere I’ve worked you start to see that in chunks of the code base.
You don’t have to. It’s certainly avoidable. But as things get more and more complicated people will just opt for that to save themselves time. Java’s design unintentionally encourage it when the going gets rough. Even when using an IDE that can add the correct types to the list for you, that’s what people reach for.
In my opinion, exceptions should be for really exceptional situations - things you can't realistically expect and/or which just signal a programming error (e.g. a precondition being violated). They aren't usually recoverable locally and should bubble up the stack, so you can handle them at some boundary (e.g. so you can serve up a 500 page to a request without crashing the entire server for everyone).
For things that are expected to go wrong sometimes, algebraic data types (including Result types) are better. They are part of the type system, so everything you can do with types, you can do with them, including making functions parametric over whether they return an "error" or not. Exceptions need extra mechanisms so that functions can be parametric over them (e.g. Swift has "rethrows", Java has... nothing). They aren't really composable with anything else.
I haven't heard anyone anywhere say that "typed exceptions are an antipattern" just "typed exceptions _as implemented by Java_ are an antipattern". The general problem is well set forth by Anders Hejlsberg, Bill Venners and Bruce Eckel in _The Trouble With Checked Exceptions_ [1]. Briefly, typed exceptions in Java are not genericizable. Everything must be known at compile time. That is to say, you cannot write:
Or give up and erase all the type information by saying `throws Exception` or `throws Throwable`.
Genericizing throws in particular was tried in Midori [2] and worked out really well (by report). In addition, several less-than-completely-obscure languages are starting to experiment with the algebra of effects in general (as opposed to error handling in particular). Pony [3], OCaml [4], and others are experimenting with bringing what Koka [5] (among others) to the masses.
Only, hardly anybody writes it that way, so people forget that it's possible. Why don't people use exception polymorphism in that way? I am not sure, but I believe it would make code too verbose, since every function containing a virtual method call to something that might throw would have to be parameterized that way.
> I trust that the "typed exceptions are an antipattern" people know what they're talking about, but I really don't understand the reasoning behind that position.
The reasoning is that they force a contract upon implementations that they may not be able to implement in spirit.
Let’s say a library defines a container interface that says a method can throw a NoSuchElementException.
Now, I implement that interface to implement a disk backed container. What do I do when that code encounters an IOException? Swallow it (very bad), throw an NoSuchElementException, possibly wrapping the IOException inside it, if possible (highly confusing, as now the class changed the meaning of NoSuchElementException), or throw an unchecked exception that wraps it instead?
Most Java code would pick that last option, and you end up with a nice interface that says that a call can throw NoSuchElementException, but callers must be prepared to get a RuntimeException that wraps any other kind of exception.
That makes the implementation adhere to the interface, but only in name.
(Note that the writers of the interface, the implementation of the disk-backed container, and the user of that container often all will be different teams at different organizations)
So, why declare that typed exception, in the first place?
But isn’t the actual advantage here that it gives me the ability to handle that error case specifically? I mean stuff like IO errors can happen everywhere at any time anyway, but if I need to do something specifically if there is no such element, isn’t that a plus? Otherwise, I’d have no chance to handle that error if I can do so.
I think the argument is that there is probably a better way of expressing that in code than exceptions. Perhaps returning an Either<Data, ErrorCode> kind of value.
There’s also an argument that that’s not truly an exception because it should be expected that an I/O error is a reasonable possibility. Things like OutOfMemory or VirtualMachineError are more what they may be thinking exceptions should be saved for. Truly unexpected things.
Also nice: a throw in Swift is like a return! It is setting the exception in a specific register and then returns.
The ‘throws’ signature indicates to the caller two things: the user needs to use some exception handling, and the callsite needs to handle the special return register in case it is filled with an exception
This way you do not need to do expensive stack walking
Typed throws (of errors, swift does not support exceptions) are for pretty specific usage scenarios - there was a years-long recurring discussion around adding them to the language (which I was an opponent).
They are useful within your own module, e.g. when they aren't part of an exported API, as an alternative to other error handling methods.
You can support them in generic utility methods like map, because you will just rethrow the typed throw.
Otherwise, it is meant for systems programming or use in embedded environments - basically the 'leaf code' that doesn't have upstream variability or independent upstream dependencies. It is meant to indicate things like 'errno' in a POSIX API. I actually feel it is a poor functional fit there; the valid error results differ both across errno-setting functions and across UNIX implementations of a particular function. The symbolic assignment (semantic error code to errno numeric value) is also variable across implementations. However, this allows the compiler to know errors are an int-sized stack type, vs a witness of a heap-allocated object.
To look at it differently, errors are meant either for recovery or for indicating a general failure for code to attempt to clean up from. Once you delegate an error to other code by rethrowing it, you no longer are conveying proper knowledge for recovery - so there's no purpose to having it be typed; all you can really do is try to fail gracefully.
A specific example - something like Swift Data is a poor fit for typed throws, because the database layer itself is adaptable. My Application doesn't know whether a failure is due to the local disk being out of space or a transient network issue connecting to a remote server, and attempting to recover from these in my application code is a pretty bad pattern because I'm baking in assumptions about a particular configuration of Swift Data across my application code (or into applications which use my module).
My opposition to the feature is that it is never required (you can indicate failure in the return signature via something like Result), adds overall language complexity, and is likely to be misused in cases where it doesn't provide value - it just adds ABI complexity over simply documenting expected Error types.
>What's the reason behind their prevalence in almost every language?
Unexpected errors are inescapable when you consider OOMs and other things so its almost required to support that. Ending scopes and bubbling up an error without a ton of unwrap boilerplate is actually really compelling.
OOMs are usually a bad thing to make into an error because there is no way to recover from them. You might as well just kill the process instead of making people try to handle it.
I'd also say there is a bit of a 'when you have a hammer…" going on. Java in particular spent a good portion of its formative years with relatively few language features, so there was a lot of cobbling them together in interesting ways to solve new classes of problems.
Exceptions became a way to capture diagnostic context for where an error happened and a (limited) view of state, so a lot of Java server applications use them as part of their issue reporting and diagnosis process.
Other languages may expect say the application to halt on unexpected errors, and to build tools to locally evaluate the core dump (such as crash reports and symbolication in Apple-land).
This does have some influence on developers in terms of the ramifications of a failure (failed request vs stopped application/server), so it can have interesting effects in how seriously a team evaluates potential errors and designs for recovery/cleanup.
> What's the reason behind their prevalence in almost every language?
More often than not it's not the caller that is responsible for handling errors/exceptions.
When you force the caller to take care of every single error, you end up with unreadable boilerplate code which hides the actual logic. There's a reason why Rust ended up with the `?` syntax sugar.
On top of that exceptions will occur. You can't pretend they won't and kill the app if they do. Again, even Rust and Go ended up adding handlers for their brain-dead panics.
Exceptions (when wielded correctly) end up simplifying your program. You develop for the happy path (mostly), and let code at the higher level of hierarchy make decisions about unhappy paths. That's how you get Erlang's supervision trees (https://erlang.org/documentation/doc-4.9.1/doc/design_princi...)
IMO the problem with tools that are great "when used correctly" is that if they don't force that "correctly" part
or the feature works in such a way that people just fall into correctly due to path-of-least-resistance, then people don't use it correctly. This, again IMO, is why people have problems with Exceptions. It is that they don't have these qualities and they are almost universally used incorrectly... thus the new languages have eschewed them much like they eschewed heavy handed OO abstractions. They were tried and found to be lacking for their intended purpose and alternatives are being tried.
Right, that’s the Java issue. It becomes very easy to just add “throws Exception“ all over the place to silence warnings when things start to get tricky.
Because Swift forces the keyword ‘try’ (or a variant) before calling code that may throw it ends up being a lot of work to try to avoid the issue just by making everything throw. It’s much easier to do the correct thing and just handle the error in a smart place.
I think this also applies to dynamic typing. Nowadays we see a shift back to typed languages because most people who use dynamically typed languages don't bother add even type hints (which is awful in a JS/Python codebase).
I’m not familiar with Rust, but Swift has three options and it sounds like it may be similar.
In Swift code that throws when called must have “try” in front of it, making it really obvious where that’s going on. Your three options:
try - calls the code and either returns the like normal or an error that you’re forced to handle in a catch.
try? - calls the code and returns the value or nil (null value) if an error is thrown.
try! - calls the code and returns the value. If the function throws an error your app panics.
It’s quite nice. You can choose to handle the error when you need to. If you don’t really care about the specifics of the error and just want to treat any kind as a failure try? cleans up your code.
And try! lets you avoid writing boilerplate when you know it’s impossible for the error to be thrown but the compiler can’t deduce that from the source alone.
An exception allows you to handle exceptional errors at any level, without having to handle errors, or write a single line of code, at every other level. You can assume perfection, and put that catch at the level of the abstraction where there's an actual concern for the exceptional, keeping all the other code simple.
I've always worked strongly in the physical world side of software, like network stacks, test equipment, robots, etc, so I can't see a sane alternative, that doesn't involve in increasing the LOC by 20%, and being error prone (it's trivial to accidentally eat an exception if you're relying on returned values).
If you don't want to bubble up the exception to the user, that's trivial too. You just catch it at whatever level you choose, handle whatever, then return something nice.
> More generally though, I wish we could avoid having exceptions to begin with. What's the reason behind their prevalence in almost every language?
Because nobody really wants low-level stuff like divide by zero, memory errors, index out of bounds wrapped as a `Result type ? Both Rust and Go have panics. Every function would effectively return a `Result` type since any non-constant expression can hypothetically return an error.
The article isn't about Swift; it's about the history of concurrency on mac's dating the to the last millennium.
The article doesn't even really explain the modern push - the difference between threads and fibers, or between actors and concurrency domains, or most critically between x86 and ARM in their memory models.
> Apple’s first Macs with dual processors came in 2000
There was also the Power Macintosh 9500/180MP back in 1996, equipped with two 180 MHz PowerPC 604e CPUs.
MacOS didn't support multi processors natively (it couldn't schedule different programs on different CPUs or anything), so it was only useful as basically a "Photoshop accelerator"
40 comments
[ 2.6 ms ] story [ 96.1 ms ] threadMore generally though, I wish we could avoid having exceptions to begin with. What's the reason behind their prevalence in almost every language?
Naively, untyped exceptions in Swift always felt like an obvious type system loophole that we should be closing as soon as possible.
I trust that the "typed exceptions are an antipattern" people know what they're talking about, but I really don't understand the reasoning behind that position.
In Java a given method either doesn’t throw any exceptions, or only throws the named ones and their children. So nothing, or maybe just InvailArgumentException (and its descendants).
However there’s a lot of different kinds of exceptions. So very quickly you tend to end up with one of two things. Functions have a big list of possible exceptions (an A, or B, or C, or F, or G) or the far more common in company code: throws Exception. Don’t bother to list stuff, just say “I throw stuff”.
Except, that’s all a lie. Because every function implicitly can throw RuntimeException and its children. Things like OutOfMemory or StackOverflow.
So basically all code everywhere realistically has to be aware of exceptions. A lot of people get lazy and don’t bother to list the actual kinds of exceptions they throw or pick one that’s a runtime exception so they don’t have to add the ‘throws’ to the signature to every single method.
And how do you solve calling a method with a checked exception? Well the right thing to do is use a try block, but so often people just add it to their own signature and pass the buck.
So a lot of the benefits just sort of get lost.
Swift now has three options: doesn’t throw, throws something, throws of type X. You can’t have multiple types. And you can’t pretend the exception doesn’t exist and just let it bubble up, you have to put a ‘try’ on the statement line to do that.
As someone who works in both languages Swift’s approach does seem like an improvement. I’m not sure how big.
But just in Swift, being able to explicitly state the one type of the exception that may be thrown seems like a very nice add for type safety and reducing boilerplate checks.
With that said, it is true RuntimeException and its subclasses are unchecked exceptions. Thankfully, those exceptions tend to be things like IllegalArgumentException, NullPointerException, etc., which are still pretty serious but not as bad as the two examples given.
Thanks for the correction.
Despite Thread.stop() being deprecated, it is still used in things like application servers to deal with user code which is not completing in a specified timeframe.
> So basically all code everywhere realistically has to be aware of exceptions. A lot of people get lazy and don’t bother to list the actual kinds of exceptions they throw or pick one that’s a runtime exception so they don’t have to add the ‘throws’ to the signature to every single method.
Not just that, there are features like lambdas where the core language features were not designed with exceptions in mind. There are third party libraries which don't have allowances for proper failure cases. So you will have code adapted to wrap their exceptions into runtime exceptions for interoperability.
Of course, there's no guarantee the code which did not provide an interface which allowed for exceptions will not have unexpected side-effects when runtime exceptions or errors are being thrown.
> However there’s a lot of different kinds of exceptions. So very quickly you tend to end up with one of two things. Functions have a big list of possible exceptions (an A, or B, or C, or F, or G) or the far more common in company code: throws Exception. Don’t bother to list stuff, just say “I throw stuff”.
Ideally, someone creating an API thinks about how the others utilizing code will accomplish recovery or graceful failures, and craft an API to account for this. Unfortunately, the errors are often handed up, which really only accounts for broad failure behavior and corresponding cleanup (such as failing the corresponding HTTP request with a generic 500 error).
The heavy use of adapter patterns in Java means that the errors being handed up may be specific to the configuration, such as failures coming from the network layer of a database backend. It is crucial for the adapter interfaces to think about failure scenarios and recovery, but it unfortunately doesn't happen often very often.
So in most cases, Java should just throw Exception, because the only way a developer can really attempt recovery is to break encapsulation and operate based on knowledge of the inner workings of the object they were handed.
But there’s a pretty good argument that the way exceptions are implemented in Java makes the easiest thing to do simply be to start tacking “throws Exception“ on the end of tons of methods once you start to have trouble. Everywhere I’ve worked you start to see that in chunks of the code base.
You don’t have to. It’s certainly avoidable. But as things get more and more complicated people will just opt for that to save themselves time. Java’s design unintentionally encourage it when the going gets rough. Even when using an IDE that can add the correct types to the list for you, that’s what people reach for.
For things that are expected to go wrong sometimes, algebraic data types (including Result types) are better. They are part of the type system, so everything you can do with types, you can do with them, including making functions parametric over whether they return an "error" or not. Exceptions need extra mechanisms so that functions can be parametric over them (e.g. Swift has "rethrows", Java has... nothing). They aren't really composable with anything else.
Genericizing throws in particular was tried in Midori [2] and worked out really well (by report). In addition, several less-than-completely-obscure languages are starting to experiment with the algebra of effects in general (as opposed to error handling in particular). Pony [3], OCaml [4], and others are experimenting with bringing what Koka [5] (among others) to the masses.
[1]: https://www.artima.com/articles/the-trouble-with-checked-exc...
[2]: https://joeduffyblog.com/2016/02/07/the-error-model/
[3]: https://www.ponylang.io/
[4]: https://ocaml.org/manual/5.2/effects.html
[5]: https://koka-lang.github.io/koka/doc/index.html
The reasoning is that they force a contract upon implementations that they may not be able to implement in spirit.
Let’s say a library defines a container interface that says a method can throw a NoSuchElementException.
Now, I implement that interface to implement a disk backed container. What do I do when that code encounters an IOException? Swallow it (very bad), throw an NoSuchElementException, possibly wrapping the IOException inside it, if possible (highly confusing, as now the class changed the meaning of NoSuchElementException), or throw an unchecked exception that wraps it instead?
Most Java code would pick that last option, and you end up with a nice interface that says that a call can throw NoSuchElementException, but callers must be prepared to get a RuntimeException that wraps any other kind of exception.
That makes the implementation adhere to the interface, but only in name.
(Note that the writers of the interface, the implementation of the disk-backed container, and the user of that container often all will be different teams at different organizations)
So, why declare that typed exception, in the first place?
I think the argument is that there is probably a better way of expressing that in code than exceptions. Perhaps returning an Either<Data, ErrorCode> kind of value.
There’s also an argument that that’s not truly an exception because it should be expected that an I/O error is a reasonable possibility. Things like OutOfMemory or VirtualMachineError are more what they may be thinking exceptions should be saved for. Truly unexpected things.
This way you do not need to do expensive stack walking
They are useful within your own module, e.g. when they aren't part of an exported API, as an alternative to other error handling methods.
You can support them in generic utility methods like map, because you will just rethrow the typed throw.
Otherwise, it is meant for systems programming or use in embedded environments - basically the 'leaf code' that doesn't have upstream variability or independent upstream dependencies. It is meant to indicate things like 'errno' in a POSIX API. I actually feel it is a poor functional fit there; the valid error results differ both across errno-setting functions and across UNIX implementations of a particular function. The symbolic assignment (semantic error code to errno numeric value) is also variable across implementations. However, this allows the compiler to know errors are an int-sized stack type, vs a witness of a heap-allocated object.
To look at it differently, errors are meant either for recovery or for indicating a general failure for code to attempt to clean up from. Once you delegate an error to other code by rethrowing it, you no longer are conveying proper knowledge for recovery - so there's no purpose to having it be typed; all you can really do is try to fail gracefully.
A specific example - something like Swift Data is a poor fit for typed throws, because the database layer itself is adaptable. My Application doesn't know whether a failure is due to the local disk being out of space or a transient network issue connecting to a remote server, and attempting to recover from these in my application code is a pretty bad pattern because I'm baking in assumptions about a particular configuration of Swift Data across my application code (or into applications which use my module).
My opposition to the feature is that it is never required (you can indicate failure in the return signature via something like Result), adds overall language complexity, and is likely to be misused in cases where it doesn't provide value - it just adds ABI complexity over simply documenting expected Error types.
Unexpected errors are inescapable when you consider OOMs and other things so its almost required to support that. Ending scopes and bubbling up an error without a ton of unwrap boilerplate is actually really compelling.
Exceptions became a way to capture diagnostic context for where an error happened and a (limited) view of state, so a lot of Java server applications use them as part of their issue reporting and diagnosis process.
Other languages may expect say the application to halt on unexpected errors, and to build tools to locally evaluate the core dump (such as crash reports and symbolication in Apple-land).
This does have some influence on developers in terms of the ramifications of a failure (failed request vs stopped application/server), so it can have interesting effects in how seriously a team evaluates potential errors and designs for recovery/cleanup.
More often than not it's not the caller that is responsible for handling errors/exceptions.
When you force the caller to take care of every single error, you end up with unreadable boilerplate code which hides the actual logic. There's a reason why Rust ended up with the `?` syntax sugar.
On top of that exceptions will occur. You can't pretend they won't and kill the app if they do. Again, even Rust and Go ended up adding handlers for their brain-dead panics.
Exceptions (when wielded correctly) end up simplifying your program. You develop for the happy path (mostly), and let code at the higher level of hierarchy make decisions about unhappy paths. That's how you get Erlang's supervision trees (https://erlang.org/documentation/doc-4.9.1/doc/design_princi...)
Because Swift forces the keyword ‘try’ (or a variant) before calling code that may throw it ends up being a lot of work to try to avoid the issue just by making everything throw. It’s much easier to do the correct thing and just handle the error in a smart place.
In Swift code that throws when called must have “try” in front of it, making it really obvious where that’s going on. Your three options:
try - calls the code and either returns the like normal or an error that you’re forced to handle in a catch.
try? - calls the code and returns the value or nil (null value) if an error is thrown.
try! - calls the code and returns the value. If the function throws an error your app panics.
It’s quite nice. You can choose to handle the error when you need to. If you don’t really care about the specifics of the error and just want to treat any kind as a failure try? cleans up your code.
And try! lets you avoid writing boilerplate when you know it’s impossible for the error to be thrown but the compiler can’t deduce that from the source alone.
I've always worked strongly in the physical world side of software, like network stacks, test equipment, robots, etc, so I can't see a sane alternative, that doesn't involve in increasing the LOC by 20%, and being error prone (it's trivial to accidentally eat an exception if you're relying on returned values).
If you don't want to bubble up the exception to the user, that's trivial too. You just catch it at whatever level you choose, handle whatever, then return something nice.
Swift is getting typed throws now because the untyped ones require memory allocation, and so aren't suitable for embedded programming.
Because nobody really wants low-level stuff like divide by zero, memory errors, index out of bounds wrapped as a `Result type ? Both Rust and Go have panics. Every function would effectively return a `Result` type since any non-constant expression can hypothetically return an error.
If only it was a problem to begin with ;)
The article doesn't even really explain the modern push - the difference between threads and fibers, or between actors and concurrency domains, or most critically between x86 and ARM in their memory models.
Would someone please write that article?
libdispatch source code is an enlightening read for those interested in diving deeper into the C Runtime powering Swift concurrency.
There was also the Power Macintosh 9500/180MP back in 1996, equipped with two 180 MHz PowerPC 604e CPUs.
MacOS didn't support multi processors natively (it couldn't schedule different programs on different CPUs or anything), so it was only useful as basically a "Photoshop accelerator"