As a counterpoint to some of the negatives here, I’m generally impressed with the effort the Swift team goes to in making most features compatible without runtime support. It would be easy to just say “new swift features require a compatible OS” but a fair amount of this stuff actually works. It’s sad it doesn’t all work, but everything is a trade off and I’m thankful for the efforts put in.
C++ does have language features that are heavily supported by the language runtime, including dynamic casts and exceptions. That support is not any better able to be patched to support future language evolution than Swift's runtime is; it's actually quite a bit less patchable in practice. The main reason C++ programmers don't have frequent back-deployment issues around these features is simply that C++ has largely abandoned the idea of evolving them at all, and most C++ programmers pretend that they do not exist.
There are a handful of other C++ features that technically require support from the language runtime, like aligned operator new
, but which are less integrated into other support. Formally these features also have back-deployment problems, but by their nature, I think programmers have found it easier to work around those limits. We've been able to do similar things in Swift for similar sorts of features, but not every feature cleanly fits that bill.
Dynamic casts are, not coincidentally, one of the biggest reasons we made the decision not to try to back-deploy the reflective / type metadata parts of parameterized protocol types. We could not find an acceptable way to back-deploy dynamic cast support for these types, so if we didn't do anything, casts were doomed to misbehave on old OSes. Intentionally introducing behavior that varied so wildly by target OS seemed much worse than locking down the things you could do with these types. And there's no way to prevent dynamic casts if you allow these types as generic arguments.
I'd consider std::any
an example of C++ successfully evolving dynamic casting in a backwards-compatible way even though it does not involve the C++ dynamic_cast
feature. "Will the runtime have to be updated to support boxing this new thing in std::any
?" is not even a particularly meaningful question to ask, while for Swift's Any
it is.
There are massive downsides to C++'s approach of doing everything that can be done in a library in a library, and I certainly don't think that Swift should do the same. I think Swift has successfully shown that baking things which could be "just libraries" into the language results in a much better language, and on the whole the tradeoff of some things needing runtime updates rather than building a much less usable feature that doesn't is very worth it. I just think it is disingenuous to claim that backdeployment is an actual problem that comes up in practice when writing C++, and therefore Swift hasn't made a tradeoff.
This is a very good point. All the use-cases I had are for applications that don't really integrate with the OS common IO (e.g. web servers).
I don't have much experience with Windows, but presumably lots of their system APIs are exposed in C#. Could someone from that side of the curtain chime in with how do they pull that off?
Coincidentally, I was listening to some old interviews with Anders Hejlsberg (lead architect of C) and ran into this bit which sounds remarkably similar (at 17:50) 'Look Back' on C# - BDL2046 - YouTube
... probably the biggest influence was when we added generics, which was from .NET 1.0 to .NET 2.0. That was a big change and it had lots of new op codes in the runtime and new metadata formats and so forth, but then we made what I think in retrospect was probably a mistake, in saying that the .NET runtime is a Windows component and it ships with the Windows operating system and it cannot run side-by-side with other versions of .NET.
And that in a sense, put handcuffs on evolving anything in the runtime, and there was a number of years there where really, we sort of bent over backwards to do everything in the compiler instead of in the runtime.
But now with with .NET core, where we've finally gotten to where I think we should have been in the first place, which is: you can install runtimes in a sub-directory, they can run side-by-side you could delete it all again, and it's gone it's gone, like it never was on your machine. At that point you can have different capabilities in the runtime, and we're starting to look at at things now that we couldn't have done before.
This is a tangent for sure, but std::any
uses existing C++ typeid
(a feature that does require runtime support), and does not support downcasting. The comparable feature isn't adding new types to put in std::any
, but adding downcasting support to any_cast
. If we're lucky, the info provided by typeid
would already have inheritance information in it, but if it doesn't, there'd be no way to backwards-deploy that feature, and you would indeed get behavior that "varied wildly by target OS".
EDIT: to clarify, by downcasting I mean "I put a UIView equivalent into a std::any
and tried to get it out as a UIButton".
DOUBLE EDIT: I just thought of a way to do it without relying on the type info for the value inside the Any having inheritance information! This is what happens when the Swift compiler/runtime engineers say "we're not sure whether we can backwards-deploy this" and later on say "good news, we've figured out how to backwards-deploy this".
They don’t. Windows APIs are exposed as either C interfaces or COM interfaces. (C++/WinRT is COM under the hood.)
And for what it’s worth, for decades Windows apps written in C++ shipped with the C++ runtime they linked against. This is what caused DLL hell; bad installers would overwrite newer runtimes with older ones, breaking apps.
In Windows 10, Microsoft decided to adopt the same approach as macOS, and now ships a single “universal” C++ runtime with the operating system.
I've met the Regex error requesting to use iOS 16, but "any" worked just fine with oldest installed iOS 14.5 and target 13.0
The language runtime on older operating systems is fully able to support the any
keyword — the issue being referred to here is that the older runtimes cannot fully support parameterized protocol types. So this code can compile for older OSes:
let x: any Sequence = 0..<10
let y = x as? any Collection
But this code can't:
let x: any Sequence<Int> = 0..<10
let y = x as? any Collection<Int> // error: Runtime support for parameterized protocol types is only available in iOS 16.0.0 or newer
you should have to try let variable: [any YourProtocol<T>] = [...]
, then enumerate & use it
As mentioned up-thread, there's a straightforward workaround for almost all such use-cases, which is to wrap the type in a struct
, like:
struct MyProtocolWrapper<T> {
var value: any MyProtocol<T>
}
It's unfortunate that you have to do this in the short term, but it's also not that much of a problem in practice.
I agree that a newer version of Swift should work on older versions of macOS. Several older versions of Swift up to the most recent 5.7 work on Linux Ubuntu 18.04 or 20.04 and on Windows 10, no newer version of Linux or Windows needed.
It's a fact that it's been different on macOS for several years but in my opinion that doesn't justify that it should stay like that for years to come. Please Apple / Swift Team consider more backward compatibility.
A good example of a C++ feature that depended on runtime support and didn't get timely support on older OSes was thread_local
. Apple OSes didn't support it for a good few years after upstream clang and libc++ added support, in Xcode 8, and you still needed to target iOS 9 or OS X El Capitan to use it.
On the Swift front, we are continuing to work on functionality that will make it easier for platform vendors to back-deploy new APIs:
It would be interesting to see this back deployment feature extend to other declarations such as new types in the future too.
The first problem is that the OS itself has frameworks using a specific version of Swift, and your application is dependent on the frameworks which ship in a specific version of the operating system. Multiple versions of swift in a single process would mean incompatibilities - for instance, the String instance your application creates may have a different binary format in memory than the ones the Swift library inside the framework was built to accept.
It could be possible to ship a new OS minor version with tweaks to support new features (see: concurrency), but the assumption of applications running on the system is that breaking changes will not be added as a surprise in minor/maintenance releases.
The second problem is that the OS will sometimes gain new API at the native library/kernel level to better support features being added to newer Swift versions. For things like concurrency, there was a separate implementation (with different performance and scheduling) that needed to be written to support the back port.
Of course the implementation and optimization of Swift varies between platforms. Taking advantage of kernel and other advancements on a particular OS is a given. However, this should not prevent deployment on some earlier versions of macOS.
I assume that Swift 5.8 will be deployed on Linux 20.04 and Windows 10, we won't need to ask our customers to update their Linux or Windows. But on macOS we will have to upgrade to macOS 14 and we will have to ask our customers to upgrade to macOS 14 as well.
It is desirable at some point to take a step forward at the OS level for multiple reasons. However, forcing our customers to update macOS every year to use the latest Swift features is very restrictive.
Important to note, neither Linux nor Windows are ABI-stable WRT Swift, and as consequence of that none of their system frameworks can expose Swift APIs.
swift on linux and windows is like the wild west. you have a lot of freedom, and you can evolve faster because you are a pioneer and there is not a lot of existing support infrastructure. but that is also a drawback: there is not a lot of existing support infrastructure, so you have to do most things yourself.
swift on macOS is like the settled east. you don’t have as much freedom and you basically have to wait for the surrounding society to upgrade before you can advance. but you benefit from the settled and mature ecosystem of that world.
so “very restrictive” is very much point-of-view dependent. you could also argue that the lack of existing frameworks on linux (and windows especially) is also “very restrictive” in a different way.
Let's be very clear here. There are sometimes new features in Swift releases that can only be used when running on a new OS release on Apple platforms. There are also many new features in Swift releases that do not require a new OS release. It is not all-or-nothing and never has been.
The fact that Swift features are sometimes tied to the OS on Apple platforms is a fact of life. Swift being integrated into the OS is what enables Apple platforms to provide native Swift APIs in the OS. It is also what enables Apple to use Swift internally in the implementation of the OS. If Swift had no ability to achieve these things because it insisted on layering on top of the OS, it would never have been released; it would be a toy project with no investment from the company. This language exists because it is able to be tied to the OS.
Being integrated into the OS does burden Swift development sometimes. Apple's development teams do try our best to make features available on earlier OSes, because we understand that features don't actually help you if you can't use them, and most code can't use features that only work on the latest OS. But that doesn't just happen automatically; it takes real engineering work, and that work comes with an opportunity cost because we don't actually have infinite resources. And sometimes the best trade-off is to focus on making the language better in other ways, with the understanding that eventually programmers will be able to roll their minimum target forward.
This conversation started in the context of a feature which we have been able to make work almost perfectly in back-deployment. The only restrictions are that you can't use these new types directly as generic arguments — you have to wrap them in a struct
— and you can't dynamically cast to them. It is pretty easy to live within those restrictions and still make use of this feature. It took actual work to make that happen; for example, we had to figure out a way to diagnose the problematic uses so that programs didn't just fail to launch on old OSes with no explanation. It's taking ongoing work to make that happen; we actually just fixed a bug in it, where in some cases the compiler will do something that requires OS support when it doesn't technically need to. But we were happy to find a way to do it, because we knew that making this feature available to more people was going to be really valuable. So it's a little frustrating that, all of things, this is what's given rise to this all-or-nothing discussion.
Perhaps the benefits of ABI stability are not so much.
The primary benefit of ABI stability is that it allows Apple to use Swift in system frameworks (e.g., UIKit or SwfitUI). Without ABI stability that wouldn't really be possible.
As a slight generalization of that, it also allows older binaries to run with newer dependencies. The Swift runtime and system frameworks like UIKit are one example, but in e.g. an internal distribution scenario, a stable ABI also allows you to update a common dependency of multiple applications, such as a helper library. Nowadays shared third-party dependencies aren’t so common on the Mac (and have never been possible on iOS), but when ABI stability comes to other platforms like Linux and Windows, they can adopt this technique.