Source Compatibility

What is the Swift stance on source compatibility?
Does the Swift 4.0 semantics need to be supported ad infinitum, or is there some deprecation paths that ones can take (which might take a few major versions)?

Sometimes there are pitches that have been “obstructed” due to them being source breaking.
This leads me to wonder if we are stuck with bad/dated decisions forever.

Lay-contributor's answer, not official policy here.

There are two kinds of Swift releases:

  • "Non-breaking release": Language changes that would break any existing Swift code are not permitted. Any code that was valid before should continue to build and function the same way.

  • "Source-breaking release": Some language changes may affect existing, valid code, either making it invalid or changing its behavior. A source-breaking release will add a new version to the compiler's -swift-version flag; specifying the previous version will cause it to emulate the old compiler's behavior in situations where it's changed, and usually emit deprecation warnings telling the user what to update for the new version.*

However, things are not quite as black-and-white as this. A non-source-breaking release may sometimes include very small changes—bug fixes, new overloads—that could theoretically break existing code, as long as we believe the breakage will be very small or nonexistent in practice and the benefit is worth the risk. This is very much a judgement call and the core team is typically involved with these decisions.

Contrariwise, a source-breaking release is not a free-for-all—even in those versions, we require a strong motivation to break source compatibility. Generally, the existing behavior must be "actively harmful"—you must be able to point to specific problems it causes and demonstrate that they can only be solved by breaking source compatibility. Mere preference is not enough.

When source stability comes up in evolution discussions, people are usually saying one of two things:

  1. This proposal cannot be accepted for the next release, because the next release is non-source-breaking. It will have to wait until a future source-breaking release, and the implementation will need to continue emulating the old behavior in old language versions.

  2. This proposal cannot be accepted even in a source-breaking release, because the existing behavior is not actively harmful. Unless there is some sort of major overhaul of the language where we decide to accept "just because" changes—which doesn't seem likely—there is no future version where we will accept this change.

Which one they mean depends on the context.

So in short, other than ABI-breaking changes, and leaving aside the need to have a Swift 5 mode which emulates the old behavior, there are no decisions we are completely stuck with if the problem is bad enough. But we have to balance the badness of breaking source compatibility against the badness of the problem at hand, and we value source compatibility pretty highly.

Hope this helps!


* We have retired -swift-versions in the past, but we don't ever plan to retire -swift-version 5 because it is used in swiftinterface files, which we've promised future compilers will continue to support. I don't know whether or when we could retire -swift-version 4 and -swift-version 4.2.

11 Likes

"Source-breaking release" : Some language changes may affect existing, valid code, either making it invalid or changing its behavior.

Is there example proposal of this invaild or changing behavior. Such as if i upgrade version of Swift, and compile my project, all the build error is the Source-breaking change? Sometimes i may confused between frameworks update and Swift language update.

This is a very earlier example from SE of a proposal that introduced a breaking change (by removing syntax). swift-evolution/0002-remove-currying.md at master · apple/swift-evolution · GitHub.

1 Like

IMHO, the idea that compiler updates don't break code is unrealistic. Maybe it works on iOS, but I haven't had a single update since 4.0 that didn't break a number of things in our server-side application, both locally on the Mac and also later on Linux. Examples include expressions that suddenly fail to compile due to being to complex, the compiler leaking memory, shared object files suddenly not being where they're supposed to be, type inference that previously worked stops working, FoundationXML is being moved out of Foundation etc. Sometimes these things break in dependency and then it's not easy to fix them.

Many other languages "fix" this problem by having a) good tooling for switching compiler versions and pinning them for a specific project, and b) backporting important things like security fixes to older versions to minimise breakage. In fact, if Swift ever wants to become enterprise ready, this is one of the key requirements - enterprises typically don't want to update their codebases every couple of months.

3 Likes

Examples include expressions that suddenly fail to compile due to being to complex, the compiler leaking memory, shared object files suddenly not being where they're supposed to be, type inference that previously worked stops working

I think the OP's question (and Brent's response) was primarily about deliberate source breaks, whereas your point seems to be about accidental source breaks, due to compiler bugs. I think there is a significant difference between the two. In the former case, there's nothing you can do except switch to an older compiler, if possible. In the latter case, you could file a bug report and possibly submit a PR with a fix.

With library evolution, in principle, you could mix and match compilers if a compiler bug has a serious effect, so that you compile the bug-triggering code with an older compiler and the rest with a newer one. I agree that in practice, doing something like that might not be easy because of the lack of tooling as you've pointed out.

IMHO, the idea that compiler updates don't break code is unrealistic.

Hopefully, we can get to a point where that's not the case. Upgrading the compiler should be exciting, not nerve-wracking. :slight_smile:

4 Likes

You’re speaking to rather a different issue. The original question is a design question: under what circumstances do we consider changes that break source? With the exception of the FoundationXML change (which lies outside of the compiler, runtime, or standard library), none of the above are by design.

1 Like

You both have a point, but if the promise of backwards-compatible updates is broken so regularly, I wonder whether the issue is one of "not trying hard enough" or whether maybe the idea that people should always immediately update the compiler isn't just too utopian to even be pursued; as mentioned, I'm not aware of other languages that do it quite like that; Java maybe comes close (installers traditionally don't carefully distinguish between minor versions), but even there, people don't just jump from a major version to the next without a carefully planned migration.

Also,

yes, Foundation might not be the stdlib, but IMHO it's so integral to being able to develop Swift applications that it really should be treated as if it were part of the stdlib.

Keep in mind that while 4.0 established_source_ compatibility, ABI stability didn't happen until 5, and now Module stability with 5.1. So while generally the source-code itself should not have large breaks after 4.0, what that source code compiled into was still in flux until recent versions.

Of course that doesn't preclude the reality that updates might still break things. With Apple's platforms is probably less of an issue. Hopefully more effort will be invested in server/linux management issues now that some of the big milestones have been reached!