[Pitch] Standard name for common low level C interfaces

There continues to be a need for Swift code to directly use low level C interfaces provided by the operating system platform. These are exposed by the modules in stdlib/public/Platform including Glibc, CRT, WASILibc, Android, Musl, as well as Darwin in Apple SDKs. The fact that they all have different names requires cross platform code to start with an ever growing and tedious to maintain chain of #if os(...) and #if canImport(...).

#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#elseif canImport(Musl)
import Musl
#elseif canImport(Android)
import Android
#elseif canImport(WASILibc)
import WASILibc
#elseif canImport(EmscriptenLibc)
import EmscriptenLibc
#elseif os(Windows)
import CRT
import WinSDK
#else
#error("Unsupported platform")
#endif

This topic has come up a few times before in Pitch: The CStdlib module and [Pitch] Cross-Platform C Library Access , and mentioned in Using the C library from Swift: thoughts as well. Let's first examine what the current modules contain, which is a mix of the following

  • C Standard Library
  • POSIX
  • Traditional low level BSD interfaces like <sys/queue.h>
  • Other platform specific low level interfaces, such as <os/lock.h> on Apple platforms

Naming these modules after the platform is a poor convention for many reasons. It's ambiguous - what does Darwin mean? Everything in usr/include? Everything in libSystem? (It's neither one of those things.) Even the non-Apple platforms aren't perfectly descriptive, for example Glibc covers Linux kernel headers as well as glibc.

More importantly though, the disparate names obfuscate that there is actually a lot of overlap between these modules. e.g. If a Swift developer is looking for something from <string.h>, they have to discover all of the different system module names and carefully construct a onerous import chain at least 10 lines. All before writing any actual code! This is an inferior experience over C, where a simple #include <string.h> is all that's required.

I propose a standard name for the module covering the low level C interfaces provided by the operating system platform: CSystem. It's at approximately the same level as the System module, but is the C interfaces with minimal Swift overlay. Like the System module, and the headers CSystem covers, it is not generally portable. Swift code will require platform specific conditional compilation in the same places as equivalent C code. However, it can use a consistent and easy to remember import name, similar to how the C headers share consistent names even though they have different contents on different systems.


Why not CStdLib?

The modules contain more than the C Standard Library implementation, and in some cases the CStdLib implementation is incomplete.

Why not POSIX?

POSIX doesn't define everything that newer C standards define in the C Standard Library, so POSIX would be an incomplete description as well. Many of the covered systems do not have a full POSIX implementation either.

Why not separate CStdLib, CExtensions, POSIX, and other modules?

POSIX defines a lot of the C Standard Library headers, and lots of extensions to their contents as well. For example in POSIX the CStdLib header <stdio.h> is required to have functions that use ssize_t from the POSIX <sys/types.h> header. On a system that supports POSIX, it's not usually possible to separate its C Standard Library and POSIX implementations. Clang modules do not permit a header to be covered by multiple modules.

What about finer grained splits?

macOS 15.0/iOS 18.0 tried making modules for each C Standard Library header such as stdio_h and string_h. This allowed for precise names that mapped exactly to the C headers. However, it proved to be a rather significant compile time slowdown, clients had to manage a un-Swift-y long list of imports, and the technique was reverted back to larger modules in macOS 26.0/iOS 26.0. (The individual modules are still present, but they just import the larger modules.)

Why not stdlib?

The Swift runtime is referred to as the Standard Library Standard Library | Swift.org , so it would be confusing to call the OS platform interfaces stdlib.

What about SystemLibC, PlatformCLib, CPlatform, CC, libc, clib, C/C11/C23?

LibC/CLib suggest C Standard Library, or specifically the libc library, but these modules contain more than either of those things. C or C11 (or any other C standard name) has the same issue, just scoped to CStdLib. Platform is a term that isn't used in any common modules right now, Systemis the closer existing term. CC is evocative of the C compiler rather than a C library.

14 Likes

We call the Swift standard library Swift. So why not C?

4 Likes

It's more than just the C standard library. It's the C standard library + all of the non-standard extensions that folks pack into the C headers.

2 Likes

Yeah that, C suggests that the module would be just the C standard library to me, similar to C11. Added that above.

Question: how does this proposed import compare with what I currently get when I import Darwin.C?

I would say that the C name is too short, but we already added the @c attribute.

Just like Cocoa = AppKit + CoreData.

My thought is maybe we can also invent a new name like PlatformFoundation / PlatformCommon here to replace those. (On Windows, it is CRT + WinSDK and may extent in the future)

#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#elseif canImport(Musl)
import Musl
#elseif canImport(Android)
import Android
#elseif canImport(WASILibc)
import WASILibc
#elseif canImport(EmscriptenLibc)
import EmscriptenLibc
#elseif os(Windows)
import CRT
import WinSDK
#else
#error("Unsupported platform")
#endif

Thank you for undertaking this, :heart: I agree that cross-platform Swift needs it. For implementers, I think it would help to generally scope out what a platform C overlay included here should have going forward, as so far these platform C overlays are just grab bags of whatever its implementer wanted, as you note. Perhaps tough to do given existing precedent, but may help @3405691582 with splitting OpenBSD later and so on.

Does this mean C/C++ modules have largely failed or are there efforts underway to enable more fine-grained imports again? Not saying this lightly, as I know these modules' efforts you deal with are very complex, just trying to get an idea of how Swift devs should structure their own module maps for their C libraries. Maybe the platform overlay has special challenges in this regard that an arbitrary C library does not.

CSystem would be equivalent to Darwin. I haven't really thought about submodules, but there's no reason there couldn't be a CSystem.C or CSystem.CStdLib if we wanted.

I like the name tie-in with the System module because these C interfaces are pretty close to it in terms of what they do. "Platform" isn't a bad name per se, but we've already used "System" for something similar so I like keeping that term.

It is pretty tough to say what should go in these modules, especially in the non-POSIX environments. Embedded systems don't always have a complete C99 implementation even. Probably best to save for a future pitch, or leave to the platform experts.

No I don't think that Clang modules have largely failed, we're just finding that performance wise there's a sweet spot between "module per header" and "single mega module for everything". Fine-grained imports can be accomplished with fine-grained submodules, it's very fine-grained modules that are problematic.

The C Standard Library has a special challenge in that those headers are layered between the C++ Standard Library headers, Clang headers, and C Standard Library, in a lot of cases glued together across search paths with #include_next. It is a particularly complex problem, and one that I think deserves its own pitch.

1 Like

Off-topic/nitpicking, but isn’t Cocoa == AppKit + Foundation?

Off-topic/nitpicking, but isn’t Cocoa == AppKit + Foundation?

@diggory It's actually AppKit + CoreData + Foundation. But since UIKit/AppKit already reexport Foundation, I omit it.

The actual framework does not matter. The idea is that people can import a shim package and ignore the underlying tech in most common use cases.

For example,I recently try to add Android support for my packages which usually only involves add import Android next to Glibc. It's a big pain once Swift and ecosystem decide to add a new platform to support.

1 Like

Is the pitch to essentially type alias the long chain of imports to one import or actually have a module that unifies the imports? Because each mobile in the long chain does not have the same API so if it's a new module, we'd need to decide what's included and what's not. And if we are proposing that, why not just use System and expand that if it's missing APIs?

3 Likes

And if it is just a module that re-exports the import of the platform-specific libc, that's not a great band-aid either because it's not just the names of the modules that are different. A bunch of APIs have subtle differences with respect to things like optionality (a function might return a something_t on Darwin and a something_t? on Linux, and you have issues where some APIs require specific preprocessor symbols to be defined before including them (like _GNU_SOURCE), and that has consequences for explicit module builds. Even if you have a single name for the module, that just cleans up one part of the code—you still need to check the actual underlying module sometimes to know what you're getting and code defensively against it.

6 Likes

That's not my question though. I'm asking informationally, what is specifically in Darwin.C today, and how does that compare with what would or would not be in your proposed CSystem?

The reason I ask is because Darwin.C is (afaict) not just the C standard library.

I would also note that the standard library has some flexibility so the shape of the standard library is also inconsistent even if the interfaces are scoped to just the C standard.

For bikeshedding, why CSystem over SystemC?

One problem that I have with either of the variants though is that what guarantee do we have that it is the system C library? I could be building with a fully statically linked custom C runtime and not be using the system C library.

I actually think SystemC reads a little nicer, but Googling the name turned up https://systemc.org/ so I switched to CSystem to be unambiguous. I got the "slap 'C' on the front" from SwiftNIO's C targets. Maybe SystemC is really niche though and the overlap doesn't matter?

Sure but if you use the CSystem module then you're basically using a random set of headers that doesn't match the library you're linking?

That's specifically called out in the pitch:

Like the System module, and the headers CSystem covers, it is not generally portable. Swift code will require platform specific conditional compilation in the same places as equivalent C code.

This is the same issue you have in C, <stdlib.h> has pretty different contents on different systems. Even in Clang, <stddef.h> will define max_align_t differently on different systems. Swift modules will frequently have system specific code too, e.g. SwiftUI has a significant number of differences between macOS and iOS, Foundation has differences on Apple platforms vs others. This pitch is not trying to make all systems have consistent interfaces, it's just trying to solve the import chain of doom.

1 Like

Looking in my SDK, it's most of the C standard library, and what appears to be a collection of interfaces from I think libsystem_c? CSystem would be exactly Darwin in this pitch, not just Darwin.C. (But I see where the naming makes that a little confusing.) None of these are just the C standard library.