`Sychronization.Mutex<T>` in Standard Library, but restricted to iOS 18?

I was super thrilled to see Sychronization.Mutex<T> in Swift 6: swift-evolution/proposals/0433-mutex.md at main · swiftlang/swift-evolution · GitHub

But for some it can't be used in iOS 17 despite being based on os_unfair_lock from iOS 10?

stdlib/public/Synchronization/Mutex/Mutex.swift
stdlib/public/Synchronization/Mutex/DarwinImpl.swift

But when I "Jump to Definition" in Xcode 16 with iOS 18 SDK I get this signature instead:

@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
@frozen public struct Mutex<Value> : ~Copyable where Value : ~Copyable {
...

Is there some way to use this on iOS 17?

Apologies if I'm misunderstanding something... I had thought that I could use newer Standard Library features so long as I built against the newer version. Or is it that a static Standard Library version gets added to the OS, and it doesn't matter what I build against?

New types in the standard library are generally not back deployable, so a new type like Mutex regardless of what it's based on, needs to be deployment gated on the first version it appeared. This is an unfortunate limitation right now in Swift.

6 Likes

It would likely be possible to expose the same implementation in a package.

This would be a polyfill rather than back-deployment - the package Mutex wouldn’t be considered the same type as the stdlib Mutex - but since libraries (Apple’s SDKs, the stdlib, any 3rd-party libraries) generally use mutexes as an implementation detail and do not expose them as API, this likely wouldn’t be an issue for most developers.

Maybe we could even make an official polyfill, kind of like we wanted to do with the stdlib preview package way back.

4 Likes

Thanks all for the (obvious) explanations... I'm not sure what I was thinking. :sweat_smile:

On that note, anyone know how I might replace Builtin.addressOfRawLayout to provide a local impl of this same type? :laughing:

Can you not already? Builtins are now exposed publicly AFAIK.

Probably simplest to just separately allocate it instead. The performance loss should be very small unless you have a huge number of locks.

to clarify: what does 'separately allocate it' mean exactly in this context?

1 Like

I think the suggestion is to use OSAllocatedUnfairLock, which is an existing Darwin platform API that uses the same underlying mutex implementation. The main difference between Mutex<T> and OSAllocatedUnfairLock is that the latter requires a separate allocation. For the majority of uses of a lock, the separate allocation won't be a significant overhead.

5 Likes

Yup, or if you need to deploy even further back, use the underlying unfair lock directly and use UnsafePointer.allocate to make the storage for it. (OSAllocatedUnfairLock is essentially exactly this plus some API refinements to make it harder to misuse in Swift Concurrency contexts)

5 Likes

FWIW, for the time being you could also use (or copy the implementation of) SwiftNIO's NIOLockedValueBox<T> which is the same thing but has to allocate of course.

4 Likes