SE-0117 – The Proposal of Doom

Proposal SE-0117 is causing quite the ruckus. It’s actually a bit amusing to watch as people clamber against preventing inheritance by default.

In case you haven’t noticed… Swift really wants to move as much as possible to be statically defined as possible. This isn’t about “static is better” and “dynamic sucks”, it’s about the Core Team’s belief that being able to author code that the compiler can help you reason about is vastly safer than having code the compiler cannot reason about.

There are two very real and tangible side effects here:

  1. Resiliency
  2. Performance

Resiliency

By default, the code that Swift wants you to write wants to put you in a sport where you are not pigeonholed into a design that you do not want in the future. Today, when you create a class, if you forget to omit the final keyword, then that’s it! It’s a breaking change to later close down that class.

That is exactly the type of design error that Swift is trying to prevent when providing sensible defaults.

Performance

If the compiler can remove all of the virtual dispatching required in a dynamic type hierarchy, performance can get measurably better. Even with all of the optimizations of objc_msgSend and friends, it’s still magnitudes slower than static dispatch.

I get the argument: pragmatic programmers claim that they need the ability to work around bugs in frameworks that ship because developers make mistakes. Yes, they do… and code bases that use inheritance for this are writing hacks in their code. Should this be prevented? No… however, this proposal doesn’t actually say that it should be prevented either.

The problem is this: Swift is still in its infancy. Sure, version 3 is coming out soon, but really, let’s call it what it is closer to: version 0.3. It’s suitable for certain classes of applications, but it’s certainly not suitable for large-scale teams (100s of developers) to author code large swaths of code in it (I’m talking in the hundres of thousands or millions of lines of code here). Sure… you can, but it’s painful and expensive (e.g. see the upcoming Swift 2 to Swift 3 migration!).

What’s most important for Swift to get right up front is the underlying model. What’s the right set of defaults for everything to produce code that can benefit from tools to help analyze for correctness? Also, what are the right set of defaults to guard against code authors doing things wrong?

Taking a Step Back

Swift has two fundamental ways of expressing types: structs and classes. The primary difference is not that classes support inheritance and structs do not. The primary difference is that structs are value types and classes are reference types.

Swift could completely remove the idea of inheritance out of classes and they would still be important to have in the Swift type system.

Why is this important? Simple. The way in which we start to model future Swift types is not around do I need inheritance or not, it’s around do I need a raw value type, a reference type, or value semantics (usually a mixture of a struct API surface and an internal backing reference type store).

As an API author, if I need to have a value type, I cannot use inheritance. And more important, especially with regards to many of the arguments against this proposal, you cannot fix any issues with these struct types with inheritance either. It’s at this point that I find your arguments extremely weak: if it is so crucial that inheritance be enabled by default for class types, why are you not more concerned about Swift’s focus on value types and using value semantics for APIs? After all, there are pretty much no APIs in Swift’s libraries that you will be able to patch this way.

[Un]Safely Breaking Out

Let’s be honest here… the crux of the argument is that there is some error that has happened in the library you’re consuming and it’s either closed source or you are unable to modify the source for any number of reasons… so how do you fix it?

With unsafe code.

There’s absolutely nothing in this proposal that prevents Swift from providing tools to get you access to what you need. For example, imagine a world where you could download a developer Swift module that contains all of the unoptimized code.

This would allow you to write something like this:

class SuperHack : @unsafe YoDontSubclassMe {
	    @unsafe override dontTouch() {
		    // Implement your super hack
	    }
}

Why is this better? Well, I think it’s better for a variety of reasons:

  1. The code author was able to have vastly more performant code with their other design all consumers
  2. The specific use case that you needed to “fix” is clearly documented in your code as being unsafe; future maintainers will know this in a compiler verifiable way (e.g. not a comment)
  3. Future updates to a framework can easily be audited (e.g. all hacks can be used and logged against specific versions of frameworks)

So let’s maybe not get so melodramatic that providing no inheritance by default on classes is the end of the world?

SE-0117 – The Proposal of Doom