Conditionalising function declaration on OS version

How do I declare different versions of a function for different OS versions? e.g. one using rethrows on macOS 14 and earlier, and typed throws on macOS 15 and later (because in my current case I can only do the latter when the Failure associated type is defined on AsyncSequence, which is only for macOS 15).

if #available(…) … isn't helpful because it doesn't work for function declarations.

#if os(…) isn't helpful because it doesn't let you specify versions.

@available(…) isn't helpful because while it does have the obsoleted parameter, the compiler doesn't recognise the mutual exclusivity and erroneously complains that the function is being redeclared.

The intent, of course, is to use the new API (Failure, for typed throws) if the minimum OS version is 2024 or later, and the traditional rethrows otherwise. This logically works because the old version will still work on old and new OS versions (it's just suboptimal because it lacks static typing).

5 Likes

I have found a potentially feasible solution using associatedtype :

Solution

Would #if compiler(>=6) work?

#if compiler(>=6)

func f<E: Error>(_ g: () throws(E) -> Void) throws(E) { ... }

#else

func f(_ g: () throws -> Void) rethrows { ... }

#endif

The limitation of availability is that all of the possible versions of your library have to be able to coexist in the same binary, so you can't have strictly mutually exclusive different versions of a function with different availablity. Maybe you could have the rethrows version be always available, but deprecated in macOS 15, and make the typed-throws variant available in macOS 15 and later. The two versions would still have to be declared in a way that can coexist, though.

3 Likes

I was (potentially) looking at something like a compiler check to workaround what looks like a 5.10 bug (with one type delivered from a 5.10 compiler and another type delivered from 6.0 and up)… but was kind of thinking it might also just be an Anti-Pattern™. Could you think of any examples off the top of your head in the Swift Project that performs this check (delivering two versions of a declaration) in a good (or at least not-great-but-good-enough-and-also-not-terrible) way?

I would say that using #if compiler checks to work around compiler bugs is completely legitimate; in the end, you're allowing whatever compiler the client developer is using to build a binary with the tools they have, and that binary should still be able to run on any OS version the deployment target specifies.

2 Likes