EP 360 · Isolation · Mar 30, 2026 ·Members

Video #360: Isolation: Mutex

smart_display

Loading stream…

Video #360: Isolation: Mutex

Episode: Video #360 Date: Mar 30, 2026 Access: Members Only 🔒 URL: https://www.pointfree.co/episodes/ep360-isolation-mutex

Episode thumbnail

Description

We explore the most modern locking primitive in Swift: Mutex. It has some serious smarts when it comes to protecting nonsendable state across threads in a synchronous manner, but it also has a serious bug that you should be aware of.

Video

Cloudflare Stream video ID: 82e245948f9e9972a5cb3e258ddcfb45 Local file: video_360_isolation-mutex.mp4 *(download with --video 360)*

References

Transcript

0:05

And finally we have written a thread safe banking class that can manage a collection of accounts. We are allowed to open accounts, deposit into accounts, transfer between accounts, and compute the total deposits across all accounts. And we can do all of this in a thread-safe manner. We were able to fire up a bunch of tasks and hammer the bank with tons of transactions, and at the end of that we had the correct amount left in all accounts. Brandon

0:28

And the tool that made this possible was OSAllocatedUnfairLock . It is a modern locking mechanism that takes advantage of modern Swift concurrency tools to prevent us from doing unsafe things with our data. But, this tool only works on Apple platforms, which is fine for apps, but we want to show a cross-platform option too. Swift ships a Mutex type that provides the same “lock-protected state” ergonomics: you store your state inside the mutex and access it only via withLock , which keeps the compiler in the loop about sendability. That gives us a good baseline for isolation on Linux and Windows without falling back to NSLock .

1:10

And Mutex employs a few extra tricks to make it even more usable than OSAllocatedUnfairLock , but unfortunately at the same time due to a really bad Swift concurrency bug it is also not as safe as it could be.

1:25

Let’s take a look. Mutex

1:30

To use the fancy new Mutex in Swift we need to import the Synchronization framework: import Synchronization

1:35

…and then all we have to do is swap out the OSAllocatedUnfairLock for Mutex : let accounts = Mutex(Account.ID: Account)

1:41

…and well, we’re done! Everything compiles and the tests run and pass.

1:58

So, you may think there isn’t much more to say about Mutex , but you would be wrong. There are subtle differences between Mutex and OSAllocatedUnfairLock that are worth knowing about, and we want to take some time to dig into those details.

2:09

The first difference between Mutex and OSAllocatedUnfairLock I want to demonstrate is how strict they are when initializing. We saw that OSAllocatedUnfairLock is way too strict and won’t even allow you to initialize with non-sendable objects: func exploration() { class NS { var count = 0 } let ns = OSAllocatedUnfairLock(initialState: NS()) } Type ‘NS’ does not conform to the ‘Sendable’ protocol

2:49

But that kind defeats the purpose of a lock. It is a very useful thing to be able to protect non-sendable data.

2:56

So, we provided an initializer that is 100% safe to use and let’s you traffic some non-sendable state into the lock: func exploration() { class NS { var count = 0 } let ns = OSAllocatedUnfairLock(checkedState: NS()) }

3:23

However, this still isn’t ideal. We used a fun @Sendable @autoclosure trick to unlock this capability, but it isn’t as permissive as it could be while still being safe. For example, the simple act of first defining a variable to hold the non-sendable data breaks things: let state = NS() let ns = OSAllocatedUnfairLock(checkedState: state) Implicit capture of ‘state’ requires that ‘NS’ conforms to ‘Sendable’

3:47

We cannot capture ns inside the @Sendable @autoclosure because

NS 4:21

This is an aspect that Mutex greatly improves upon. It is completely fine to pass the non-sendable object into the Mutex initializer, even when defined in a separate variable: let state = NS() // let ns = OSAllocatedUnfairLock(checkedState: state) let ns = Mutex(state)

NS 4:38

And it may not look safe to do this, but it is. The initializer for Mutex has special annotations that communicate to Swift that the state passed in has been transferred to a new region of isolation, and so it is no longer accessible from other regions.

NS 4:57

We can see this by trying to access something inside the

NS 5:14

These errors can be a little confusing because they seem to appear on the wrong line. A line that was compiling just fine a moment is suddenly not compiling anymore just because we added a line below it.

NS 5:33

But this is letting us know that we “sent” the state value into the Mutex and then accessed it again after, and that risks a data race. If you are willing to dig through the Xcode errors a bit you will see this comment: Note Access can happen concurrently

NS 5:51

…and it highlights the line that is causing the problem. It’s something, but it’s a little hidden.

NS 5:57

And we can go to the initializer of Mutex to see how it accomplishes this magic: public init(_ initialValue: consuming sending Value)

NS 6:03

This sending annotation is what tells Swift to transfer the value from one region to a new region. This tool is part of what is known as “region-based” isolation, which we will be getting into a bit later.

NS 6:18

There’s another place sending takes a prominent role in Mutex , and that is its withLock method: public borrowing func withLock<Result: ~Copyable, E: Error>( _ body: (inout sending Value) throws(E) -> sending Result ) throws(E) -> sending Result

NS 6:31

This is a considerably more complex version of withLock when compared to OSAllocatedUnfairLock : public func withLock<R: Sendable>( _ body: @Sendable () throws -> R ) rethrows -> R

NS 6:45

…which just uses @Sendable and Sendable .

NS 6:53

Whereas Mutex is making use of nearly every advanced tool in Swift. It uses typed throws, non-copyable types, borrowing , and sending . In fact, it uses sending 3 times! A value is sent into the argument of the body closure, a result is sent out, and then ultimately a result is sent out of withLock .

NS 7:25

This allows you to do even more things using withLock that are 100% safe, but would not be allowed by OSAllocatedUnfairLock . For example, it would be totally fine to read the count of the non-sendable object in the withLock and then construct a whole new non-sendable object. let state = NS() let ns = Mutex(state) let newNS = mutex.withLock { NS(count: $0.count + 1) }

NS 8:04

So, in one sense, Mutex is a less strict version of OSAllocatedUnfairLock because it allows you to use it more ways that are still safe from data races. However, in another way, Mutex is more strict. We didn’t have any need for it, but technically OSAllocatedUnfairLock exposes a lock() and unlock method: let lock = OSAllocatedUnfairLock() lock.lock() lock.unlock()

NS 8:34

This only works when OSAllocatedUnfairLock is generic over Void , but at this point this is just as unsafe as NSLock . As we previously saw, it is not a good idea to use a lock as a fencing mechanism without it literally protecting a piece of state.

NS 8:57

And so Mutex doesn’t offer this at all. First of all, Mutex doesn’t even offer an initializer that takes no arguments: let mutex = Mutex() Missing argument for parameter #1 in call

NS 9:06

…because a mutex protecting a Void value is pointless.

NS 9:10

Mutex has taken the very strong stance that it will not expose these unsafe tools to the outside. It does have an underscored and explicitly named unsafe version of these tools: mutex._unsafeLock() mutex._unsafeUnlock()

NS 9:23

…and who knows what strange use case needed this unfortunate escape hatch. But as long as you stay away from this tool, you can have some confidence that your protected state is indeed protected. The problem with Mutex

NS 9:37

And so we can see that Mutex mostly serves the same purpose as OSAllocatedUnfairLock , but by employing more modern tools of Swift concurrency it can allow us to do more with it that may seem unsafe at first, but is actually safe. It is able to understand when we safely traffic non-sendable objects into the mutex and traffic them out based on Swift’s analysis of region-based isolation. Stephen

NS 10:03

And so we would love to tell everyone that Mutex is a very powerful tool that everyone should be using when it makes sense, and that you can rest assured that if your code compiles, then the state held in a mutex will be 100% thread safe. But sadly, we cannot. We must caveat everything we have said so far with some very important remarks.

NS 10:23

Due to bugs in Swift’s region-based isolation it is possible to write code that compiles just fine but has major concurrency problems. Let’s take a look

NS 10:34

Before showing the problem with Mutex, let’s see that it typically does do quite a good job at letting us write flexible code that is fully concurrency safe. We will try tricking the isolation checker in Swift to make it do something bad, and see just how good it is at catching us. Let’s start with a non-sendable object inside a Mutex : func explorations() { let state = NS() let ns = Mutex(state) }

NS 10:57

We’ve already seen that messing with the non-sendable object after passing it to the mutex gets caught as an unsafe thing to do: let ns = Mutex(state) _ = state.count

NS 11:08

So that’s good.

NS 11:12

But what if we tried to be sneaky by escaping the protected state by assigning it to a variable defined outside of withLock : var next: NS! mutex.withLock { state in next = state } print(next) This would be bad because we have been allowed to escape the protected state and would be able to use it from multiple threads. And thankfully fails to compile: ‘inout sending’ parameter ‘state’ cannot be task-isolated at end of function

NS 11:43

If you really, really want to escape this state from the mutex you have no choice but to give up holding onto the state in the mutex. So, if you reassign state with a new

NS 11:57

And this is working even though

NS 12:17

We can even do something that used to be a big no-no in Swift, which is to capture mutable state in an escaping, concurrently executing closure: var count = 0 Task { count += 1 }

NS 12:33

This seems to be very unsafe because we defined mutable state on one thread and seem to be mutating it from another thread. But thanks to the sending keyword Swift knows that the count has been “sent” into the task and it will prevent any usage of count outside of that region: var count = 0 Task { count += 1 } count = 1 Sending value of non-Sendable type ‘() async -> ()’ risks causing data races

NS 13:01

So this is pretty impressive. Swift really does seem to have our back to make sure we are simultaneously allowed to write code that passes data round to different tasks and thread, while still making sure we always do it safely.

NS 13:13

However, there are unfortunately some very serious soundness holes in the concurrency checker that make this a precarious tool to use, and we still have to be careful using it.

NS 13:22

And considering how good the concurrency checker was at finding subtle problems, it may surprise you to learn that it allows something as simple as this: let escaped = mutex.withLock { $0 }

NS 13:40

Here we are escaping the non-sendable object from the mutex, and that seems really bad. Now currently this is not a problem because we are still in a synchronous context and the object is not actually being passed around to other threads.

NS 13:50

But, if we fire up Task and escape the object again from the mutex, this compiles just fine: Task { let escaped = mutex.withLock { $0 } print(escaped.count) }

NS 14:00

Again, this is technically ok because we still not accessing the non-sendable object from multiple threads. However, the real kicker is we can indeed escape the non-sendable object again after we fired up the task: Task { let escaped = mutex.withLock { $0 } print(escaped.count) } let escaped = mutex.withLock { $0 } print(escaped.count)

NS 14:19

And now we are officially in data race territory. This will absolutely access the non-sendable object from multiple threads and this should 100% be prevented by the compiler.

NS 14:29

And this can manifest itself in some really bad bugs in our code. Remember that unsafe account(for:) method that would return a Account for a given ID? Let’s bring it back: func account(for id: Account.ID) throws -> Account { try accounts.withLock { guard let account = $0[id] else { struct AccountNotFound: Error {} throw AccountNotFound() } return account } }

NS 14:44

This kind of code compiled just fine with NSLock because it has no concept of sendability or isolation. Then when we upgraded the lock to an OSAllocatedUnfairLock it stopped compiling because the trailing closure of withLock was marked as @Sendable and so it was not ok to return the account object.

NS 15:02

But here we are using a Mutex , and it is compiling. But this code is not correct at all, and in fact showed that using this method resulted in data corruption and even a runtime crash. It is a really awful bug in Swift that it is allowing us to compile this code. And for this reason we unfortunately need to be very careful with mutexes.

NS 15:19

And the sad part is that Swift used to correctly diagnose this problem. We can even copy-and-paste this code into Godbolt’s Compiler Explorer, which allows you to compile code with a variety of compilers, and we will find that Swift used to refuse to compile this code: func account(for id: Account.ID) throws -> Account { try accounts.withLock { guard let account = $0[id] else { struct AccountNotFound: Error {} throw AccountNotFound() } return account } } ‘inout sending’ parameter ‘$0’ cannot be task-isolated at end of function; task-isolated ‘$0’ risks causing races in between task-isolated uses and caller uses since caller assumes value is not actor isolated Sendability as a view into isolation

NS 15:50

I wish we had better news when it came to Mutex . It’s an incredibly powerful tool with an unfortunate defect currently. It will probably be fixed eventually, hopefully soon, and we don’t recommend staying away from it entirely, but it’s very important to know that you have to be careful with what you return from the withLock trailing closure. Brandon

NS 16:05

Now before moving on to how actors address so many of the problems we have been coming across let’s end our discussion on locks and mutexes by discussing the Sendable protocol. While locking is a legacy mechanism for isolating data, it did make use of the Sendable protocol to at least make locking a little bit safer.

NS 16:24

The Sendable protocol was proposed a little before actors, and set the stage for how Swift can tell you when you are using unsafe data incorrectly in multithreaded apps. It will let you know when you are unsafely passing mutable data across a concurrent boundary, but it doesn’t allow you to define isolation domains for safely holding onto mutable data. That’s what actors do.

NS 16:47

So let’s take a quick look at what Sendable tells us about our code.

NS 16:53

The Sendable protocol has no requirements in Swift: public protocol Sendable : SendableMetatype { }

NS 16:58

…but it is what is known as a “marker protocol”. It is only there to provide a signal to the Swift compiler that this type is safe to use from multiple threads simultaneously. Any type that is not sendable will not be allowed to be passed across concurrent boundaries, such as closures that can be executed on multiple threads.

NS 17:17

Most value types are naturally sendable because they just hold simple data and are copied when passed around and so there’s no way for multiple threads to ever mutate the value at the same time. So, something like this is sendable: struct Counter { var count = 0 }

NS 17:35

And for internal and private structs you don’t even need to conform to Sendable because it is done for you automatically. But you always can annotate it if you want: struct Counter: Sendable { var count = 0 }

NS 17:45

Reference types on the other hand, are almost never sendable out of the box because they hold mutable state, and the reference to that state can be passed around to multiple contexts without copying, making it much easier to accidentally mutate state from multiple threads simultaneously.

NS 18:01

So, something like this is not sendable, and Swift knows it: class AuditLog: Sendable { var history: [String] = [] } Non-final class ‘AuditLog’ cannot conform to the ‘Sendable’ protocol Stored property ‘history’ of ‘Sendable’-conforming class ‘AuditLog’ is mutable

NS 18:22

And then as soon as we add some non-sendable state to something sendable, we get an error letting us know it is no longer sendable: class AuditLog { var history: [String] = [] } struct Counter: Sendable { var count = 0 let auditLog = AuditLog() } Stored property ‘auditLog’ of ‘Sendable’-conforming struct ‘Counter’ has non-Sendable type ‘AuditLog’

NS 18:44

So we can see that Swift does correctly track what is and is not sendable.

NS 18:50

There is a companion annotation to the Sendable protocol called @Sendable , which represents closures that are safe to execute simultaneously from multiple threads. So, defining a function that takes a trailing, sendable closure like this: func operate(body: @Sendable () -> Void) { body() }

NS 19:09

…will enforce that we are only allowed to pass closures to this function that are proven to be safe to execute concurrently. In particular, the closure cannot capture any non-sendable state.

NS 19:21

To see that this keeps us honest, let’s write a quick function that constructs a bank account, which is non-sendable, and then tries to capture it in a closure passed to operate : @Test func sendable() async throws { let account = Bank.Account(id: UUID()) operate { print(account.balance) } } Capture of ‘account’ with non-Sendable type ‘Bank.Account’ in a ‘@Sendable’ closure

NS 19:48

Swift is correctly letting us know that this is dangerous to do. Without this error we would technically be accessing the balance in the account from multiple threads, and because Account has no internal locking, that is not safe to do.

NS 20:10

On the other hand, this would be completely fine to do with Bank because we did lock it down: let bank = Bank() operate { print(bank.totalDeposits) }

NS 20:26

And if we defined an operation helper that was completely synchronous and did not take a @Sendable closure: func syncOperate(body: () -> Void) { body() }

NS 20:43

…then it would be completely fine to capture non-sendable data in it: let account = Bank.Account(id: UUID()) syncOperate { print(account.balance) }

NS 21:08

This is showing us that Swift understands the difference between passing non-sendable data around to functions that are going to operate in an immediate and synchronous manner, versus functions that may escape out to perform the work from multiple threads.

NS 21:23

So, we see that Sendable is quite powerful at preventing us from doing incorrect things in our code. So, does that mean we should just make everything sendable? Should the Account class be sendable? We already made the Bank sendable by locking everything, so if we had just gone the extra mile to add locks to the account would that have helped?

NS 21:48

Well, that only brings more questions that we have to contend with!

NS 21:53

Let’s try making Account sendable: final class Account: Identifiable, Sendable { … }

NS 21:59

This obviously doesn’t compile yet because Swift knows this class is not thread safe. To make it thread safe we need to protect its mutable state with a mutex.

NS 22:04

Now when we did this for the Bank class we were lucky that there was just a single piece of state to protect. But in the Account we have a bit more: let id: UUID var balance: Int var balanceHistory: [Int] = []

NS 22:20

The id doesn’t need to be protected because it’s Sendable it self and is a let , and so can never change.

NS 22:30

But balance and balanceHistory are mutable, and so must be protected. One approach might be to lock each individually: let balance: Mutex<Int> let balanceHistory: Mutex<[Int]>

NS 22:50

This greatly increases the number of points in our code that threads are allowed to interleave by opening up individual locks for each piece of state in the class. This leads to very messy code that is almost certainly incorrect but impossible to diagnose.

NS 23:15

It would probably be better to have a single lock protecting both pieces of state, but then that means we have to move this data into a little inner struct and wrap a Mutex around it: let state: Mutex<State> struct State { var balance: Int var balanceHistory: [Int] } public init(id: UUID, balance: Int = 0) { self.id = id state = Mutex(State(balance: balance)) }

NS 23:46

But now we are adding even more ceremony to our class. In order to have a thread safe class we need to design a struct with all fields, hold the struct in a mutex, and anytime we are implementing logic in this class we have to decide whether it goes inside this inner State type or directly on the Account class.

NS 24:08

But let’s keep pressing forward. Next we will need to update the deposit method: public func deposit(_ amount: Int) { state.withLock { $0.balanceHistory.append($0.balance) $0.balance += amount } }

NS 24:25

And then the withdraw method: public func withdraw(_ amount: Int) throws { try state.withLock { guard $0.balance >= amount else { struct InsufficientFunds: Error {} throw InsufficientFunds() } $0.balance -= amount } }

NS 24:38

And finally we need to update the Bank ‘s totalDeposits property to go through each account’s lock to get the balance: public var totalDeposits: Int { accounts.withLock { $0.values.reduce(into: 0) { $0 += $1.state.withLock(\.balance) } } }

NS 25:04

This now makes the Account sendable, and the core of the library is compiling. But to get tests compiling we have to go through the mutex: #expect( try bank.account(for: id1) { $0.state.withLock(\.balance) } == 50 ) #expect( try bank.account(for: id2) { $0.state.withLock(\.balance) } == 150 )

NS 25:27

Now everything compiles and the tests pass!

NS 25:34

So, is this how we are supposed to do things? To get thread safety in our app are we supposed to just make more and more things Sendable ? Whenever we have a Sendable object are we supposed to be making everything it interacts with Sendable too?

NS 25:54

Well, we are here to tell you, emphatically: no . Sendability is a far reaching property of a type, and in practice very few types need to actually be sendable or even can be meaningfully made sendable. In our quest to make both Bank and Account sendable we have introduced 2 major problems in our code.

NS 26:11

First of all, the code has become a complete mess. We need a lock in each of our classes, every single method is wrapped in a big ole withLock , we’ve previously seen that mutexes are not recursive, for good reason, and so we aren’t free to call methods from other methods as that will crash the lock. And if we want to add more state to these objects we can’t simply lock each individual field, but rather we need to bundle those fields up into yet another type and then wrap that type in a mutex.

NS 26:58

And finally, we have made our code less performant than it needs to be. It may look like we have two locks in our code, but in reality if our bank has 10,000 accounts, then there are technically 10,000 and 1 locks floating around out there. Even the simple act of compute the total deposits in our bank: var totalDeposits: Int { accounts.withLock { $0.values.reduce(into: 0) { $0 += $1.balance.withLock(\.self) } } }

NS 27:15

…causes all 10,001 locks to be opened and closed. Mutexes are fast, but they aren’t free. Previously when Account was not sendable we were able to prove isolation a single time, and then access all of our state at once. That is going to be much more performant that accessing locks thousands of times. And the slower the work in accounts.withLock gets, the longer the lock is held, and the more locking contention we will get. And we will show the concretely soon when we benchmark the various approaches to isolation.

NS 27:46

So, we just want to stress that it’s worth thinking hard about whether a type should be sendable or not. There is a lot of power in keeping a type non-sendable, because then you don’t have to think at all about protecting its state. You get to write methods and properties in a completely naive manner, focusing purely on the logic of the type and not on external factors such as thread safety. And Swift will keep you in check to make sure you use that non-thread safe object correctly.

NS 28:15

But, there are of course times you will need to make types sendable.

NS 28:18

First of all, simple value types that just hold plain data are totally fine to be sendable. They are already implicitly Sendable when internal or private, and can be made publicly sendable by explicitly conforming to the Sendable protocol.

NS 28:32

Second, if you have generic types, then it’s typically a good idea to make those types conditionally sendable based on the sendability of the type parameters. For example, Optional s are sendable when their underlying Wrapped value is Sendable . That allows users of your generic types to decide when they need sendability or not.

NS 29:01

And finally, you may be forced into sendability just based on the needs of your application, and in particular, encapsulation. Next time: Actors

NS 29:34

And so what we are seeing here is that making everything sendable is not the way. Sendability should be applied rarely and surgically, and there is actually a lot of power in keeping things non-sendable. It allows you to interact with those objects in a completely synchronous manner with no locking whatsoever, and Swift can have your back to make sure you never accidentally leak that object across threads in ways that could cause data races. Stephen

NS 29:59

OK, we have now spent a lot of time talking about older-style tools for isolating objects that are not safe to use from multiple threads, primary locks. They are a very crude tool for synchronizing access to a resource that is not thread safe, and while some modern annotations such as @Sendable and sending can make these locks safer to use, they are still quite problematic.

NS 30:20

Perhaps the biggest problem is just that the locking code infects all of your core feature logic. We ended up wrapping every single method in the Bank class in withLock , and not only did that make a mess of our code, but it technically isn’t even 100% correct. Brandon

NS 30:36

Further, because locks and mutexes are not re-entrant, and because recursive locks are a whole problem on their own, we are prevented from freely refactoring our code to call little helpers from our methods because we may accidentally introduce a deadlock. Stephen

NS 30:52

And finally, because we needed to individually protect each access to the mutable state we were naturally led to a situation that caused us to lock and unlock a ton more than should be necessary. These locks aren’t free, and we over time we are going to pay the cost of locking as our feature gets more and more complex.

NS 31:09

And so by the end of these little explorations I just no longer really know how to reliably make changes to this bank class or trust that it even works correctly. Brandon

NS 31:17

And this is where actors and isolation domains formally enter the stage. Actors give the Swift compiler a static understanding of isolation domains, and this allows us to weaken the ceremony required to enforce isolation even more. Recall that when exploring locks we saw that by using more and more advanced features of Swift we could loosen the restrictions of locks while not sacrificing safety:

NS 31:42

First we saw that OSAllocatedUnfairLock used @Sendable closures for its withLock method, and that meant we couldn’t capture non-sendable state in the closure or even return non-sendable state from the closure. Stephen

NS 31:54

Then we saw that Mutex used sending instead of @Sendable , and that allowed us to start capturing and returning non-sendable state, but only if that state was never touched again afterwards.

NS 32:04

Actors allow us to go one step further where we just get complete, unfettered access to mutable state with no locks whatsoever, but the catch is that we must first prove to the Swift compiler that we are in the right isolation domain. If you cannot prove that then you must await interacting with the actor to get into its isolation domain. And you may think that will add a proliferation of await s and asynchrony throughout your code, but when used properly that is not necessarily the case. Brandon

NS 32:31

And unfortunately there is quite a bit of misinformation in the Swift community that one can simply avoid Swift’s concurrency tools, such as actors, by just using locks, but that is incredibly far from the truth. There really is no comparison because locks do not come anywhere close to achieving what modern Swift concurrency tools achieve. Stephen

NS 32:52

Let’s explore this topic deeply by recreating our bank library with actors. We will see that if approached naively we will create all new problems for ourselves, although at least the data races will be fixed. And then we will make use of some of the more advanced tools of Swift to properly use actors for implementing this library.

NS 33:09

Let’s take a look…next time! References Mutex A synchronization primitive that protects shared mutable state via mutual exclusion. https://developer.apple.com/documentation/synchronization/mutex SE-0306: Actors John McCall, Doug Gregor, Konrad Malawski, Chris Lattner • Oct 30, 2020 This proposal introduces actors into Swift. An actor is a reference type that protects access to its mutable state.” https://github.com/swiftlang/swift-evolution/blob/main/proposals/0306-actors.md Downloads Sample code 0360-beyond-basics-isolation-pt6 Point-Free A hub for advanced Swift programming. Brought to you by Brandon Williams and Stephen Celis . Content Become a member The Point-Free Way Beta previews Gifts Videos Collections Free clips Blog More About Us Community Slack Mastodon Twitter BlueSky GitHub Contact Us Privacy Policy © 2026 Point-Free, Inc. All rights are reserved for the videos and transcripts on this site. All other content is licensed under CC BY-NC-SA 4.0 , and the underlying source code to run this site is licensed under the MIT License .