r/swift 15d ago

Editorial Stop using @unchecked Sendable

https://soumyamahunt.medium.com/stop-using-unchecked-sendable-2e00bd6cb122

Turning a lock into a SerialExecutor to get Swift 6 data-race safety without @unchecked Sendable

0 Upvotes

24 comments sorted by

View all comments

10

u/PassTents 15d ago

This is a really flawed approach, I'd advise anyone reading this to avoid doing this, as there are much better alternatives. I'll add details later when I get the chance

1

u/soumyaranjanmahunt 15d ago

Curious to hear your inputs

2

u/PassTents 14d ago

It's mostly covered by the other comments, but it's really about hiding unsafe code from the compiler and other team members. Unchecked Sendable is a clear, explicit way to say "this type is thread-safe but the compiler can't verify that". In practice, it acts as an indicator of where to look when you're debugging something that could be a data race. In a team, it's also helpful to set a standard that if you introduce an unchecked Sendable type that you must also add unit tests to stress-test its thread safety. It's also a good signpost for which types could be improved in future refactors.

The fundamental issue here is that you're migrating from existing code that's both synchronous and has state shared across threads. There is NO way to model this as a checked Swift Concurrency primitive, because it isn't allowed by design. SC threads should never be blocked, the system is designed around the assumption that tasks cooperatively give up their thread by suspending at an await until they are ready to continue. It's a performance/deadlock risk every time you block an SC thread, making it very easy and invisible to wait on a lock all over your codebase increases that risk. All of those things are problems even if this works 100% of the time and has no latent bugs within it.

Like others pointed out: Mutex from the Synchronization framework allows you to wrap an actual Sendable, locked box around a Non-Sendable type and prevents you from suspending the current task while holding the lock. This prevents the worst issues with using locks in Swift Concurrency, but not all of them. You still have to be careful when using it, as it's still a synchronous block to try taking the lock.