⚠ Important packages are being released using unstable language features

I am not sure a 1.0.0 release tag on a package should be construed as an assertion that the tag works with Swift 6, Swift 60, or NewLang 2050. I have only ever understood it to mean that the release supports the tools version declared in the manifest, and any additional ones documented. As a package vendor, when a new version of Swift is released, I will try it, see if it works, and add it to the documentation. If necessary—quite often the case—I will fix some things and release a new version.

Swift has not promised never to have breaking changes, but only to keep them to a minimum and constrain them to major versions. Or have I misunderstood something?

Where these usages are spilling outside the Swift project, your thread is an excellent reminder for those of us doing it (or tempted to start) to reign ourselves in before it bites us. So I think it is good that you have brought it to everyone’s attention again. It is also a good reminder for those on the inside just how eagerly those on the outside are waiting for these particular features to be finished.

I just do not think anyone needs to fret about Swift project components interacting more tightly with each other than with external projects. The employees have keys; the clients do not. That does not strike me as unusual.

6 Likes

I believe the concern relates to “swiftc 6.0 in Swift 5 mode”, not “Swift 6” the hypothetical future language mode. I think Karl could be making this distinction more clearly though.

1 Like

Okay, let's imagine it. What's the very worst that can happen if Apple just full on removes the underscored functionality, not just from Swift 6, but from the Swift 5 compatibility mode as well. What would happen?

  • Anyone on existing tools would see nothing. Existing compilers and code would continue to work as is.
  • Anyone updating to the Swift 6 compiler may see compilation failures. This is pretty much guaranteed to happen anyway, given that Swift 6 will likely be breaking in various ways. At worst these changes aren't supported by the updater so they have to be done manually.
    • Given that the previous version compatibility modes have never been 100% compatible, it seems likely there will still be breakages for public features even in compatibility mode.
  • Package authors, seeing their current version is broken under the new compiler, will release a new version to fix it. At very worst, this will have to be a major release that drops compatibility with older compilers, if necessary. It's possible that they choose to ship a hybrid codebase, depending on the language's ability to conditionalize the change. It's up to the project whether or not they continue maintaining the version compatible with the older compiler.

To me, this seems like a pretty standard major version upgrade process. Given that most libraries will want to do a major release to take advantage of major language changes, most well maintained libraries will be updating anyway, offering opportunity to apply any needed fixes. At worst, libraries would need to be updated to replace attributes which aren't supported in the Swift 5 compatibility mode. While Apple is under no versioning requirement to support such attributes, it seems likely that the impact of removing them will at least be considered, given that are in use in the community.

Alamofire shipped its first use of an underscored attribute in 5.5, @_disfavoredOverload, so that we could disambiguate between a function with an updated parameter label and the older, mislabeled one. While semantic versioning allows fixing bugs like that in a patch release if found in a timely manner, it wasn't found for over a year, making that approach untenable. So rather than release a major version simply to fix a mislabeled parameter, the attribute was used, as the actual deprecation functionality in Swift doesn't provide disambiguation by default. Given Alamofire 6 will likely require Swift 6 anyway (depending on the timeline), at very worst we'd release a version of 5 that would only support building on Swift 6 as part of a transitionary period between the two.

4 Likes

You're taking the "Swift 6" example too literally. I must be doing a really bad job of explaining this. Let me quote Ben again;

There are no source stability guarantees for underscored attributes. None. Zero. At all. The idea is that the compiler team reserves the right to change them in ANY way at ANY time.

You just shipped a release with @_disfavouredOverload. That attribute could just up and disappear tomorrow in Swift 5.5.3 for all anybody knows (it doesn't have to be a major version), and all of your users will suddenly see their builds break, and everyone down the chain will see their builds break. The compiler team reserves the right to do that, and if it happens, it would be nobody's fault but yours - because you used an unstable language feature.

"you need to deal with the consequences"

That's what it means. You're rolling the dice for everybody who depends on your package, whether they know it or not, whether they agree to those terms or not. Sorry if that sounds dramatic or harsh - I'm just trying to explain what this lack of source stability means. They absolutely should not go in important libraries which people depend on; no matter who wrote them.

Maybe. But again - there is absolutely no obligation, and in fact these features have that underscore
:arrow_right: precisely to opt out of that obligation :arrow_left:.

So... fingers crossed that the next minor update won't break every app that even indirectly uses Alamofire, eh? :crossed_fingers:

And yeah, a lot of important features are, unfortunately, left in underscore limbo. But that doesn't mean you should just build your mission-critical infrastructure on a foundation of crossed fingers; it means we urgently need to enhance Swift so you don't have to.

This attitude, where nobody takes this fact seriously, and just uses unstable features anyway because it's "likely" they won't break, is exactly the problem.

3 Likes

While technically true, what you’re worried about is practically impossible. Apple would never replace such an attribute in a patch version, if only because they’d have to replace their own usage immediately. And given they’ve shipped public API with these attributes means the actual requirements for change, rather than the technical ones, are much higher than you think. We aren’t operating in a vacuum with only abstract rules.

But let’s say they do drop the attribute. The outcome is really no different: a new release to either adopt the replacement attribute or drop the usage altogether. Not really a big deal.

2 Likes

That's the point. People have this attitude that it's practically impossible for this stuff to be removed, so they use them even though they shouldn't.

Anyway - I leave it to the core team. They've been pinged. It's their problem, and their responsibility to ensure the package ecosystem is healthy and stable. For my part, I've done more than enough to warn everybody that unstable features are infecting the ecosystem. I'm tired of arguing with people.

4 Likes

FWIW, I agree with @Karl completely, and I find the use of underscored features in swift-numerics, swift-collections, et al a strong deterrent from using those packages in production code. It’s a pretty clear signal that ”while this package has a 1.0 version tag, it is not actually stable.”

1 Like

By that standard the Swift language itself isn’t stable, which is ridiculous. There’s been more churn due to bugs and public changes than these attributes, so I find attempts to point to them as examples of instability rather silly. This notion that libraries shouldn’t have to make releases to support new versions of Swift flies in the face of the entirety of the language’s history. Hyperbolic threads about these attributes do nothing to help anything.

9 Likes

I’m inclined to agree with @Karl on this one: underscored language features officially don’t exist, and are therefore undefined behavior. This is particularly problematic for alternative implementations of the Swift compiler (there are a fair number on various embedded systems, etc.), which must assume that only defined behavior needs to be implemented.

At the risk of kicking the can down the road, I think we may need different levels of support in Swift: things that should be assumed to break without recourse in major versions, but aren’t going to pop out of existence in minor versions.

Such things could sidestep the Swift Evolution process as a stopgap measure. For instance, _disfavouredOverload could get implemented under a different name in Swift 5.6, but compilers up to Swift 6 would accept it with a warning anyway. At that point it would just break.

Hopefully this would allow Swift to be a little more honest: some things need to be implemented messily, and that’s probably going to be true for quite a while. At any rate, it seems clear to me that the current state of affairs is unsustainable.

1 Like

I used @inline(__always) and @_exported import while creating ARHeadsetKit, ARHeadsetUtil, and MetalFFT. It would have been impossible to make those packages without the underscored keywords. Furthermore, unsafe flags in the Swift package manager were needed to pull off automatic differentiation in the Swift release build.

1 Like

If anything, I think the issue is that the language is moving too slowly on certain features that are extremely important in practice. I think these fall into three categories:

  • Memory/optimization control (@_effects, _modify, etc.)
  • Namespace control (@implementationOnly, @_exported)
  • Requirement tweaks (@_implements(ProtocolName, Requirement), @_unavailableFromAsync)

There needs to be an immediate focus on these issues, even if it means putting off wholly novel work like distributed actors. It’s getting out of hand.

As a start, the compiler should start requiring explicit arguments to use undefined behavior. This is what @_private(sourceFile: "FileName.swift”) does, and I think that’s sufficient disincentive to avoid misuse.

Second, we should start throwing the “unsafe” prefix around where necessary instead of pretending features with undefined behavior don’t exist.

Third, there needs to be less worry about renaming or changing things. Major versions should be expected to break things, and using underscores because a name might change or a future solution may be found is unacceptable. That is the only reason I can conceive of for some of these exclusions.

Finally, if something isn’t currently possible without language features that haven’t gone through evolution yet, do something else. Fork the Swift toolchain if you are sick of waiting.

2 Likes

Finally, if something isn’t currently possible without language features that haven’t gone through evolution yet, do something else . Fork the Swift toolchain if you are sick of waiting.

Several of these features are needed on apps released to the iOS App Store, namely @inline(__always) for controlling performance. If they were forced into a separate (and not indefinitely maintained) toolchain like Swift for TensorFlow, that would seriously diminish how many applications could use them.

It also takes hours to build a Swift toolchain and a lot of expertise to get it right. That's not a feasible option for many people.

4 Likes

Then we need to acknowledge that they are part of Swift for the moment. Swift is supposed to avoid undefined behavior, for crying out loud!

The current state of affairs says a patch version of Swift can take every undocumented feature away at any moment. If that’s a problem, that must change.

1 Like

Personally, I’ve found this extremely harmful to my ability to interpret Swift code written by others. I should be able to read the contents of Swift Algorithms without seeing code that officially shouldn't work.

1 Like

If you wish to take that position, fine, but you need to find another way to describe these features, as they certainly aren't undefined. Nor are they unsafe as Swift typically uses the term. At most they're unstable, but even that is stretching it given how heavily the language relies on them to get things done. Notably, this position: "I should be able to read the contents of Swift Algorithms without seeing code that officially shouldn't work." doesn't really make sense, any more than the use of other under documented Swift APIs "shouldn't work."

If we look at the history of these attributes and how they evolved, the @_functionBuilder to @resultBuilder transition is an obvious example. Technically, Apple could've dropped @_functionBuilder entirely once @resultBuilder was finalized, but that isn't what happened. Instead, a rather lengthy evolution process took place over multiple Swift releases and when it was finalized the old syntax was deprecated, not just removed. This helped not just the community that had adopted the attribute, but Apple's use of it in SwiftUI as well. Given how widely used some of these attributes are, and how important others are, it seems highly unlikely Apple would approach their evolution any differently, even if they could technically remove them tomorrow.

I do agree that the proliferation of such attributes is getting out of hand, especially for rather simple ones like @_disfavoredOverload. (At the least, such disfavor should likely be built into deprecation.) However, I'm not sure what the community can do to help here. Try to fully define particular features to prepare them for full evolution?

5 Likes

“Undefined” means they aren’t defined as part of the public interface, which means they can disappear in a patch version.

1 Like

That's not what "undefined" means in the context of programming languages. And they are part of the public interface, which is why they have to be underscored in the first place. It's not as if we're tricking the compiler into giving us access after all. Their "instability" is strictly name related.

3 Likes

If Swift isn’t SemVer-compliant, we might as well give up entirely.

Oh, come on - you know better than that, surely?

The standard library is not distributed in source form. It comes with one compiler, and if distributed, is already compiled.

If we're going to cross that line and start calling things "ridiculous", "silly", and "hyperbolic", that argument itself is ridiculous and betrays a severe lack of comprehension.

When have you ever seen the standard library being built by a compiler other than the one it ships with? The standard library's use of compiler hacks is a well-known special case -- and yes, those hacks are not at all stable!

If you're going to be so rude, try not to also be so wrong.

These attributes commonly show up in the language’s public module interfaces, making them a defacto part of Swift’s source stability story. Additionally, they’re used internally to provide critical functionality, like the performance guarantees of _modify. So the the impact of a change of either side largely the same.

2 Likes