Avoid #if hell, please

Looking through out the source code I can quickly see that the Swift project does the same mistake as many other language projects when it starts to support more and more operating systems.

For example Thread.swift is littered with #if statements which makes it difficult to read. Another example is FileManager+POSIX.swift one file for each OS. This is a better approach as now each system support is separate files.

The problem is as Swift will be ported to more and more systems, this approach will quickly make the code unmanageable. Much better if the OS dependent implementation files are moved to separate files and subfolders. Then as a natural step, an API can be developed in order to interface the OS dependent implementation. While you think an API is unnecessary, it can help creating a OS independent interface both to the system and the programmer. An example how to not do it is Go where they just copied many POSIX nomenclatures into their own standard library which is the wrong approach I think.

If you make these changes, porting to other systems will be much easier. Also the separation between Swift dependent and OS dependent code will be more maintainable. Make these changes early (which means now) because otherwise the bad #if hell design tends to remain as there were no way out of it.

8 Likes

+100, I also think code littered with #if os(...) becomes unreadable really quickly. If it's merely setting a different constant somewhere or importing a different module it works for me but as soon as there's any logic I also think it should be split.

I'm not saying this is the prettiest code but for example in SwiftNIO, the thread functionality is split into

  • Thread.swift: all OSes, most of the logic, leveraging the (internal) ThreadOps protocol for the low-level operations like setting a thread's name etc.
  • ThreadPosix.swift the pthread implementations for ThreadOps
  • ThreadWindows.swift the same but for Windows.

(And just to be clear: This "abstraction" is zero-cost, the protocol ThreadOps is never actually used. It just exists to check conformance of each platform's actual implementation. To avoid any virtual dispatch overhead each platform defines something like typealias ThreadOpsSystem = ThreadOpsPosix and the code using this only uses ThreadOpsSystem. You could kinda say that ThreadOps is a compile-time-only protocol)

Personally I find the swift-corelibs-foundation code particularly hard to read or even edit without breaking the build, usually because the #if os(...)es which are plentiful. As a comparison: SCL Foundation's all platforms Thread.swift.

6 Likes