Printing Foundation's Version Number

Here is a Swift 6 class operating under strict concurrency checking:

final class Foo
{
   init() {
      // ERROR: Reference to var 'NSFoundationVersionNumber' is not concurrency-safe because it involves shared mutable state.
      print("Foundation Version: \(NSFoundationVersionNumber)")
   }
}

Can someone demonstrate the magical incantation to overcome Swift Concurrency here? I’ve tried…all the things.

I’ll spare commentary about the state of Swift Concurrency and its reputation.

1 Like

That's the point of Swift Concurrency, you can't. It's rather unfortunate, but the best I can say is don't fight against it.

Its slow. Its not cross platform. Its ugly. But it works for me, just now, on my machine, and thats the only guarantee I can give.

Hopefully posting something grotesque motivates someone with a more elegant answer…

func versionNumber() -> Double {
    let handle = dlopen(nil, RTLD_LAZY)
    let symbol = dlsym(handle, "NSFoundationVersionNumber")
    guard let symbol = symbol else {
        fatalError()
    }
    return symbol.assumingMemoryBound(to: Double.self).pointee
}
3 Likes

Would it work for your purposes to, in another file somewhere, do:

@preconcurrency import Foundation
/*public*/ let foundationVersionNumber = NSFoundationVersionNumber

?

4 Likes

Ha, I love this!

That's the point of Swift Concurrency, you can't. It's rather unfortunate, but the best I can say is don't fight against it.

That’s where we disagree. The reputation of Swift (and especially Swift Concurrency) has taken an absolute beating in the broader context of programming languages precisely because it makes absolutely trivial things so damn hard in the quest for absolute, academically-provable safety.

To wit: Foundation’s version number is never going to change while my app is alive. Ever. This error presents simply because Foundation was created before Swift Concurrency. You know that. I know that. But there is no “escape hatch” for me to tell the compiler: “I, a sentient human being, REALLY know what I’m doing here. Be. Quiet.” All the things I reached for first (nonisolated, unsafe, global let, Task, and the other 341 keywords associated with Swift Concurrency) just produced more of the same.

It should not be this hard. It just shouldn’t. Concurrency is a tricky problem, but Swift Concurrency has just traded one set of difficulties (reasoning about threads) for another (reasoning about a sea of syntax).

Would it work for your purposes to, in another file somewhere, do:

@preconcurrency import Foundation
/*public*/ let foundationVersionNumber = NSFoundationVersionNumber

@jumhyn Thanks, that’s what I ended up stumbling into finally. It was actually my initial attempt, as well, but I used kCFCoreFoundationVersionNumber instead of NSFoundationVersionNumber, which still produces the same error even with @preconcurrency on the import. THAT sent me down the nonisolated/task/unsafe/detached/global-actor/global-let/etc. rabbit hole and I finally just threw up my hands and came here.

No AI figured out the right approach either.

4 Likes

For the case of “I’m using something which does not present a concurrency-safe interface in a way which I nonetheless know to be safe”, @preconcurrency should be that. I suspect you were having issues because kCFCoreFoundationVersionNumber does not actually come from Foundation, but rather from CoreFoundation. It’s arguably a bug that the @preconcurrency doesn’t cover reexported symbols but perhaps there’s a good reason for that.

For the case of “I’m doing something which cannot be formalized as concurrency-safe within the bounds of Swift Concurrency”, nonisolated(unsafe) should generally be the escape hatch.

4 Likes

Unlike frameworks that are actually preconcurrency, NSFoundationVersionNumber claims to have been properly audited for Swift Concurrency (NS_HEADER_AUDIT_BEGIN(nullability, sendability) but actually hasn't been (this is pretty common in Apple's frameworks). Swift Concurrency is actually quite nice if you don't have to use Apple's SDKs.

7 Likes

NSFoundationVersionNumber claims to have been properly audited for Swift Concurrency (NS_HEADER_AUDIT_BEGIN(nullability, sendability) but actually hasn't been (this is pretty common in Apple's frameworks). Swift Concurrency is actually quite nice if you don't have to use Apple's SDKs.

This is true. Academically, it’s nice. It demos quite well in the little WWDC apps.

But in the real-world, where developers are interfacing with decades of legacy platform code, it gets very difficult very quickly. And the way out of the edge cases is rarely obvious. Unfortunately, I don’t have the option of using Swift without Apple’s SDKs.

2 Likes

Should/does this work? (I am away from my computer.)

final class Foo {
   init () {
      let u = NSFoundationVersionNumber
      print ("Foundation Version: \(u)")
   }
}

Should/does this work? (I am away from my computer.)

final class Foo {
   init () {
      let u = NSFoundationVersionNumber
      print ("Foundation Version: \(u)")
   }
}

Nope. Not if Foo is isolated to the MainActor (which it is automatically if you’re using the new “approachable concurrency” default in Xcode 26).

1 Like

I wish there were a way saying I am going to be unsafe in this block of code, please let me be:

final class Foo {
   init () {
      unsafe {
         let u = NSFoundationVersionNumber
         print ("Foundation Version: \(u)")
      }
   }
}

This would make life less painful for real world programmers. :slight_smile:

1 Like

I realize this is silly, but can't you work around this by creating a #define / constexpr variable in a bridging header that's set to NSFoundationVersionNumber then use that?

1 Like

NSFoundationVersionNumber doesn't really tell you the version of Foundation despite the name. Rather, it's a value that is exported from the framework and, for historical reasons, cannot be removed. On Apple platforms, the version of Foundation can be (more or less) equated with the OS version. On non-Apple platforms, it can be equated with the Swift toolchain version.

When using e.g. FoundationEssentials as a package instead of as an OS or toolchain component, I don't believe the package version is emitted anywhere useful, although if you have a use case for it I suspect it could be added relatively easily.