[swift-users] "business applications market" flame

Brent Royal-Gordon brent at architechies.com
Fri Jan 8 22:10:37 CST 2016

>>> The server needs to stay up, even in the face of latent bugs causing NullPointer exceptions and user-defined versions of similar errors.
>> Hold on. Swift doesn’t run in a virtual machine, doesn’t have a true garbage collector, and doesn’t (to my knowledge) have the same safety guarantees as “managed” languages like Java or C#. It’s a lot safer than C or C++, but buggy Swift programs can crash.
>> For example, my understanding is that dereferencing an implicitly-unwrapped optional (e.g. an “Int!”) that contains nil really does dereference a null pointer and causes a bus error. Otherwise every such dereference would have to have code in it to preflight the pointer, which is inefficient. Even if I’m wrong and Swift does preflight, it’s response is to trigger a fatalError that crashes the program. (Go is able to catch such a deref and turn it into a recoverable panic, but only because it installs a SIGBUS signal handler in the process, which is part of Go’s “we take over the entire process and do everything our way” philosophy.)
> All I am asking for is that the "throws" keyword on a method definition be optional - that is, a method can throw a <thingy> with the throw statement without specifying "throws", and it can be caught by a matching try/catch clause up the stack.  That is perfectly possible with LLVM and does not entail any ill-defined behavior.  LLVM is capable of and well defined for supporting exactly what I'm looking for.

Swift made a conscious choice not to include exceptions thrown through arbitrary stack frames not because it was technically impossible, but because its designers judged the costs to be too high.

The problem is this: if a piece of code is going to exit early because of an error, it has to be written to handle that early exit. Otherwise it will misbehave—fail to deallocate memory, fail to close file handles/sockets/database connections/whatever, fail to release locks, etc. In a language like Java, writing truly exception-safe code requires a ridiculous quantity of try/finally blocks. That's why nobody does it. They make judgements about which exceptions they're likely to see and which resources are dangerous to leak, and only protect their code against those specific anticipated conditions. Then something unforeseen happens and their program breaks.

This is even worse in a reference-counted language like Swift because correctly balancing the reference counts in the presence of exceptions basically requires every function to include an implicit finally block to balance all the retain counts. This means the compiler has to generate lots of extra code on the off chance that some call or another throws an exception. The vast majority of this code is never, ever used, but it has to be there, bloating the process.

Because of these problems, Swift chose not to support traditional exceptions; instead, it only allows you to throw errors in specially-marked regions of code. But as a corollary, that means that, if something goes really wrong in code that can't throw, all it can really do to prevent a disaster is crash. And currently, the only thing you can crash is the entire process.

Eventually it may become possible to create some kind of self-contained unit of code within a process; a failed precondition would only crash the unit it's in, and since that code's side effects could only be within that unit, the rest of the process could safely continue running. But right now, no such feature exists.

> You first sentence concerns me though.  What "safety guarantees" does Swift lack that Java and C# have (other than possible memory leaks because of ARC)?  Part of our communication problem is that I don't believe we agree on what the word "managed" means.  To me it means "memory management", that is, garbage collection and dynamic class loaders.  I suspect you are using it as per Microsoft's definition - a section of code that runs in the CLR that is type and pointer safe, vs. a section of code that is not (ie. "unmanaged").  FWIW, I believe that Microsoft's .NET/CLR is the only place in the computer industry that uses the word "managed" like that.

The CLR and especially the JVM try to create a safe, hermetically-sealed environment where, to a first approximation, nothing bad can ever happen. You can't crash a process without it causing a catchable exception. You can't get a reference to memory unless it's part of a properly-allocated, correctly-initialized, and still-present object. You can't cause the garbage collector to free an object that's still accessible, or leak an object that isn't anymore. You literally can't express unsafe constructs in their bytecodes. There are some escape hatches, particularly in the CLR, and of course there are bugs that make it possible to do things that shouldn't be allowed, but the goal is to create safety guarantees that are literally impossible to subvert, so that it's safe to run code from an untrusted source.

Swift is not like that. Compiled Swift code is machine code; there's just no validating it. The standard library includes constructs that can access arbitrary memory addresses. Memory management is done with reference counting, and the Unmanaged type in the library can subvert it. You can call freely into arbitrary machine code. And a crash is a crash, not an exception. There's simply no attempt to provide the level of complete safety that the CLR and JVM attempt to achieve.

>> If you have a server process that needs to stay running even if modules fail, then you’ll probably have to run those modules as child processes and monitor them.
> That's really ugly and defeats the whole idea of GCD.

GCD is meant for lightweight concurrency in a shared memory space. Though it provides tools to help mediate between activities on different queues, it makes no attempt to *prevent* activity on one queue from screwing up another queue.

> A per-thread fatal error handler is not the answer though.  A full fledged unchecked try/catch handler is what I am looking for, which can be implemented in Swift without any changes to LLVM.

Again, not in a fully safe way, and not without significant costs that Swift's designers judged to not be worth it.

Brent Royal-Gordon

More information about the swift-users mailing list