You seem to have missed the point of this post.

It is not about debating which style is better, it's about improving upon an already existing style used extensively. Why is it used so much more than canImport? Ask Apple, probably because #if os makes it clearer what is being checked for.

I agree with what Karl wrote above, and I think this is a misuse of the term "Darwin".

There are more than 4 Darwin systems. Although macOS, iOS, tvOS and watchOS are some of the most targeted ones, they're not the only ones. Within Apple, there are at least bridgeOS, audioOS, and whatever iPods used to run on; outside apple, there are at least OpenDarwin and PureDarwin. There can also be arbitrarily more Darwin systems in the future. Thus, as far as the current implementation goes, it is incorrect to equate Darwin to only macOS, iOS, tvOS, and watchOS.

Also, as far as I understand, if os(macOS) || os(iOS) || ... are used when there are APIs from the vendor (Apple) that are not available elsewhere. These APIs normally sit above the Darwin layer, e.g. SwiftUI and AppKit which is in the Cocoa layer. However, as mentioned in the paragraph above, there are more than the 4 Darwin systems, and not all of them have these APIs. So it's just wrong to replace something like

#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
import CoreData

with something like

#if os(Darwin)
import CoreData // error if a Darwin system does not have Core Data

Since the only thing that all Darwin systems share is the Darwin layer itself, whose APIs are exposed via the Darwin module, the only correct code within an #if os(Darwin) would be code that uses only the Darwin module. And for this usage, we already have #if canImport(Darwin) which in my opinion expresses very clearly what the condition is.

9 Likes

Adding to my post above,

I think #if vendor(Apple) would be a better, and more correct, solution than #if os(Darwin).

7 Likes

So I took this under consideration after it was brought up on another forum. I initially had good thoughts about this, but there were a couple of points that were also mentioned that might show this causes some confusion in implementation.

The first reason addresses your concern about there being multiple Darwin systems. Currently, no other darwin systems support swift. Even though you are correct about there being other darwin operating systems that aren't shipped by apple, unless they supported swift, there's no real incentive for making this distinction.

The second reason addresses the very specific nature of the #if vendor(Apple). If this was to be implemented there would also need to be #if vendor(Microsoft) and #if vendor(Linux). This might cause confusion between the #if vendor(Linux) check, and the #if os(Linux) check. Not implementing this would make the macro vendor(...) specific to apple and flawed in its implementation where a flag would make more sense (ex #if __APPLE__).

With that in mind, by choosing to continue in the implementation of #if os(Darwin) and choose not to get into the specifics of the darwin operating system, we are left with a good alternative to #if os(macOS) || os(iOS) ... and the confusion only exists in over thinking it.

Finally, I understand what you're saying here, and that does make sense, but another alternative to this is to just check the import of the CoreData module. Ex:

#if canImport(CoreData)
import CoreData
#endif

This alternative suffers no compromises and it's very simple to understand. Also, by ignoring the other darwin systems like mentioned before, we have no reason to believe that by checking #if os(Darwin) we'd end up on a machine without the supporting libraries.

Anyways, these were just my thoughts about this matter after a thorough conversation with some other developers. It was a very interesting suggestion!

2 Likes

I think that vendor(Microsoft) is fine, but vendor(Linux) is not very useful - Linux is not a vendor. You could have "RedHat" (or I suppose "IBM" now) or "Canonical" as vendors, but the vast majority of the Linux distributions should fall under the "unknown" vendor.

While this stance is reasonable, I think that designing the system such that we do not cut off that avenue is important. They do not currently support Swift, what if they were to become interested that? We would then have to design something new at that point. Setting up the design to accommodate it but not implement it is a nice pragmatic approach IMO.

I'm not sure that is the case. I think that some of the names (CoreData being a good example) are overlay broad and can collide. Other ecosystems avoid this with a reverse-dns naming scheme to avoid the collisions (not that I am advocating that), so you do risk collisions due to the lack of namespacing the modules. There is a tradeoff here, just one that is being taken to not matter.

5 Likes

Yes, I agree in that case there would be an unknown vendor, and the majority of Linux distros would be under it, but adding those as vendors (especially as more come up) is quite a list to maintain. Currently, os(Linux) refers to linux as a whole, and that works just fine. Adding all the linux vendors would undoubtedly cause confusion in handling that operating system

I do completely agree with you on this. There does need to be a pragmatic viewpoint on this, though vendor might not be the most optimal solution to this.

I believe that even though this is a possibility, the collision of the name is something that should be handled by the developer with respect to non-system libraries. Not only that, but a name collision will be hard to do with the API interface of the libraries having quite the margin of difference.

It could be considered a trade-off in the sense that the developer can now think about the possibility of a name collision, but couldn't they always? If you had a swift package dependency that had a name reflecting the name of a standard module, there would be a collision. That's just IMO

I think a general way of combining multiple #if conditions under a reusable alias would be the best answer; it would both solve this (for those who consider it a problem) and be useful in other areas:

#let darwin = os(macOS) || os(tvOS) || os(iOS) || os(watchOS)
#let swiftUI = canImport(SwiftUI) && !(os(iOS) && arch(arm))
#let float80 = !(os(Windows) || os(Android)) && (arch(i386) || arch(x86_64))
#let float16 = !((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64))

#if darwin && swiftUI && float80 && float16
  // ...
#endif
11 Likes

This is a very important point that @Karl makes, and any proposed addition will have to demonstrate great clarity in answering it. It's also worth noting that this topic has come up at least a few times here.

As others have pointed out, it's problematic to assume that a library such as Core Data is available simply because we're on a Darwin platform or because Apple is the vendor. Making either option available and an attractive alternative for os(macOS) || os(iOS) || ... would present problems for correctness, if developers will use it for gating OS-specific features (which, it seems to me, is what a developer who wants to write #if os... likely wants to do).

To avoid speculating on the future product roadmap of any particular vendor, I'll illustrate with a deliberately fictitious example:

Banana is a vendor that supports Swift on its three operating systems: ovenOS, stoveOS, and microwaveOS. You replace #if os(ovenOS) || os(stoveOS) || os(microwaveOS) with #if vendor(Banana), because it's more convenient, to guard some steak-cooking code. One year later, Banana ships dishwasherOS. Suddenly, the meaning of your code has silently changed without your having audited whether it makes sense, and your app attempts to cook steak in a dishwasher.

5 Likes

For what it's worth, availability (@available and #available) very deliberately works this way because of the opposite problem: when Banana ships grillOS, code refuses to compile even though it would work just fine. This happened in practice with tvOS, which is why TARGET_OS_IPHONE is true on tvOS. The lesson we/I learned from this is that for most code it's better to permit attempting to compile on an unknown architecture rather than just give up.

14 Likes

Sure, @available works in this way (with an explicit * sigil) and always has (afaik), whereas #if os doesn't and never has.

Whether that distinction was deliberate or not—and certainly one should consider whether the preferred behavior for decorating a declaration should also apply to arbitrary blocks of code—expanding an existing feature which behaves one way with additional functionality that behaves differently needs to be strongly motivated, and it must be noted that one is not then a shorthand for the other.

This goes to what I said about @Karl making a good point, in that we need clarity about what is to be achieved here. If what is sought is a shorthand for a finite set of existing OS conditions, then what's proposed here behaves differently from what is sought, and in ways that are neither visible to the reader nor testable ahead of time.

4 Likes

WWDC 2022 session 110354 "What's new in Swift" revealed that Swift is now used in the Secure Enclave, which I think implies that Swift now supports bridgeOS.

Granted, there are still many Darwin systems that Swift does not support, but we can never be sure that they will remain unsupported in the future, or that there won't be new Darwin systems with Swift support in the future. So it's best that we don't paint ourselves into a corner.

3 Likes

You can do #if os(bridgeOS) in Swift already, so that's probably correct

I definitely agree, I still think the current methods are ugly though, ie

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) // Long, hard to read
// Darwin code
#endif

or..

#if canImport(Darwin) // Awkward to use this if using Darwin-only symbols from another module, ie Foundation
// Darwin code
#endif

If you ask me, if you use #if os(Darwin), that means you're accepting that you will exclude any future or current Darwin platforms that won't support the code under #if os(Darwin), by adding && !os(NonSupportingDarwinOS).

Unfortunately it's not as simple as #if !os(Linux)

1 Like

Proposal opened here.

No, this isn’t new. The problem is that every is in a flat namespace so you do no know if the value is being shadowed locally. (e.g. I can define a module named Darwin and canImport will happily report yes even if is an empty module).

1 Like

Apple seems ~1 year away from launching realityOS, an operating system for their overly expensive AR headsets - and years afterward, reasonably priced smart glasses. I did a lot of research into this topic while creating ARHeadsetKit, and Apple has been quietly adding features to Metal and ARKit that will someday enable AR headset experiences. Well, technically they already have because I made an AR headset experience.

The last time Apple released a new platform was watchOS in 2015. This was a year after Swift was released (2014). Since the language was new, I assume there was not much fuss about adding || os(watchOS) to a bunch of macros. But now, there might be because people want API stability. I tried adding || os(realityOS) to a Swift package to future-proof it and let it run on Apple's future wearable AR devices. It failed to compile because the platform was not recognized.

I haven't fully read through the discussion here, but I wanted to point out a reason os(Darwin) would be helpful. If someone's Swift packages switch to os(Darwin) now, they will be future-proofed when Apple launches their OS for AR headsets.

2 Likes

By the nature of unannounced systems, the public doesn't know what public APIs they'll have, let alone their behaviors. So using os(Darwin) to target potential future systems isn't future-proofing, but a speculation that will likely introduce bugs in the future if any future system doesn't behave as it's assumed to.

As written upthread, in an os(Darwin) branch, the only thing that can be guaranteed is the existence of the Darwin module, but we already have canImport(Darwin) that makes the same guarantee.

7 Likes

An unannounced OS might not run Darwin at all.

Highly doubt this, considering it’s Apple.

1 Like

+1. How would the OS run Metal, which depends on Foundation and Darwin?