Using the C library from Swift: thoughts

A Ctrl-F on "import FreeBSD" in that pull in my browser turns up 81 additions, and then manually going through them all turns up 3 that also define typealiases, don't see any other real differences. If the argument is that 3-5% of the time a conditional block that requires additional platform declarations is still required, the argument is lost.

Yep, that's why I used the name _platformC instead, as the current overlays usually don't limit themselves to the libc alone, and most devs just grab whatever imports the largest platform API surface without bothering to scope it down. Considering all the module import leaks and nobody really sure what imports what, people just go big.

There are certainly a few who customize the import wart, but that's no deal-breaker- this customized version with a typealias from the linked pull:

#if canImport(Darwin)
  import Darwin
  typealias JumpBuffer = Int32
#elseif canImport(Glibc)
  import Glibc
  typealias JumpBuffer = jmp_buf
#elseif canImport(FreeBSD)
  import FreeBSD
  typealias JumpBuffer = jmp_buf
#else
#error("Unsupported platform")
#endif

would simply turn into this instead:

import _platformC
#if canImport(Darwin)
  typealias JumpBuffer = Int32
#elseif canImport(Glibc) || canImport(FreeBSD)
  typealias JumpBuffer = jmp_buf
#else
#error("Unsupported platform")
#endif

As for how to replace it with C99 eventually, it would depend what you needed in each file/module. If you were only ever using C99 APIs in a file to begin with, you simply replace _platformC with C99. If it's both, I hope to see other fine-grained partitions of these platform APIs in the future too, so you can mix and match the imports that you need.

I added support for another platform libc to another programming language a decade ago, years before I ever got into Swift, and I wrote some crude scripts back then using libclang or some such tool to automate separating out all these platform C APIs in that language itself. This work is highly automatable, both on our end in implementing it in the Swift stdlib, and then later in helping devs transition to or choose what fine-grained imports to use, assuming they don't just choose the largest API surface like import Android does today.

For now, considering most multi-platform use of Swift is fairly new, a very broad _platformC that simply replaces the broad platform overlay import wart already being commonly used is a nice dev UX improvement, nothing more. Eventually, we can figure out how we give Swift devs tools to more finely import platform C APIs, hopefully through automated tools like swift-format or go fix that automate rewriting initially broad platform imports into fine-grained ones, as nobody wants to pick out all the submodules from these giant module maps by hand. :stuck_out_tongue_winking_eye:

1 Like

The crux of my question is how often we're "only ever using C99 APIs."

If the answer is rarely, then really we are having two almost distinct conversations: the OP's, which mentions "perfectly-portable-in-C" C standard library functions and asks how to make them also perfectly portable in Swift, and yours, which is inclusive of platform-specific definitely-not-portable facilities and making them one-shot importable in Swift. Defining a C99 does approximately nothing for the latter, and aliasing the import dance does approximately nothing for the former (other than simplifying the prologue).

3 Likes

I think the answer to that is indeed rarely: the only reason they're lumped together is because both are different approaches to multi-platform code and some seem to be asserting that the _platformC alias will somehow make the transition to C99 more difficult later on. I don't get that argument, as the import wart that _platformC simply aliases is already spreading throughout multi-platform code. I suppose one could argue that by making people copy-paste that import wart everywhere, people will just stop using Swift for multi-platform code and therefore there will be less multi-platform Swift code to transition whenever C99 is ready, :wink: but I don't think that's what they intend.

2 Likes

I’ve noticed in Xcode some C headers are available as import string_h for example, could this be a valid solution?

One thing I’ve noticed in 26 betas, some functions that came from headers such as fenv.h are no longer included when importing Foundation in Swift.

Or at least, it seems like that is what happened, because we used to be able to use certain C API such as feclearexcept/fetestexcept with just one import Foundation statement.

I’m okay with replacing that code with more modern alternatives, but I don’t see this called out in the Swift section of the Xcode release notes, and I can’t seem to find a commit in Swift’s foundation projects on github that (I assume) stopped exposing many of those APIs that @scanon mentioned above should never be used from Swift.

This seems to be a change between Swift 6.1 and 6.2, would be nice to see it referenced in the release notes at least.

What platform are you seeing this on?

This is somewhat unlikely to be a swift stdlib/foundation change; more likely a change to either the platform’s C headers or the C library modulemap. But also, the fenv operations didn’t really did what you want with either gcc or clang in the past—they have started to in recent years for a few specific platforms, but as a general C language feature they have never worked consistently.

Seeing this on iOS. Makes sense that they didn’t really do what they said, in our case this stems from a third party SDK (Big Nerd Ranch’s Freddy) that hasn’t been maintained for a long time.

Probably will just port the code to use Swift’s FloatingPoint type and call it a day, since I have no way to do a diff between the platform C headers from iOS 18 to 26.

Thanks!