I downloaded Xcode 16.3 beta 1 the other day to try out this approach, and I'm pleased to report that it mostly works.
The Good
-
I can create a package with traits, and the trait names get exposed in code as active compilation conditions.
-
I can depend on this package in another package and enable the traits in the
.dependency(...)statement -
With these together and some creatively-applied
@available(*, unavailable, message: "...")statements, I can make a package that proactively tells clients that they're trying to use an API without the proper trait enabled.
The Meh
-
Conditional compilation is kind of gross. It balloons the complexity of the code when used within method bodies, and using it to conditionalize the existence of methods themselves means that there's a constant battle to make sure that the true-branch method matches the false-branch method. I will need to investigate better ways to organize the code to see if I can mitigate this. Perhaps multiple targets combined with some
@_exportedimports? Or complex source file inclusion based on trait enablement somehow? -
It's a stringly-typed API, which means it's easy to fat-finger a trait name. Yes, the compiler will tell you that the mis-typed trait, but there is zero code-completion.
-
I'm surprised that the trait applies to the entire package and not to a specific dependency. It's common to deal with packages that have multiple products, and I'm surprised that each product has to use the same set of traits. Or you could easily imagine a package that defines a "debug" trait to enable extra introspection capabilities. A client of the package would only want to use that for specific targets, not every target. The fact that you cannot specify traits on a per-usage basis seems like a pretty unfortunate design.
The Ugly
-
Xcode has no support for any of this in Xcode projects. There is no way to specify active traits when depending on a package. This is a feature that is only available to other packages. This means that, in its current form, it essentially unusable to me without the tedious chore of creating intermediate packages.
-
Additionally when editing, there is no way to discover what the compilation condition of the trait actually is. I have to know a priori how the compiler will translate my trait name into a condition. Since trait names are strings that can contain things like spaces or emoji or slashes or zalgo text... Once again I'm left completely high-and-dry by the tools to use this in any sort of way that is a single step beyond the most rudimentary:
edit: aha, it turns out that particular screenshotted trait name isn't legal. However, Xcode will still succeed building the package even though the package itself is invalid.
The point still stands though about the un-discoverability of condition names:
This feels like a step in the right direction, and I appreciate the hint to try this @tevelee! I can get this to mostly work right now, but I'm left wanting a whole lot more from the tooling.

