With Swift 6, we have now a wholly new model of the language that has every kind of information race protections built-in. Most of those protections had been round with Swift 5 in a method or one other and in Swift 6 they’ve refined, up to date, improved, and expanded these options, making them necessary. So in Swift 5 you might get away with sure issues the place in Swift 6 these at the moment are compiler errors.
Swift 6 additionally introduces a bunch of latest options, one among these is the sending
key phrase. Sending
intently pertains to Sendable
, however they’re fairly totally different by way of why they’re used, what they’ll do, and which issues they have an inclination to resolve.
On this put up, I wish to discover the similarities and variations between Sendable
and sending
. By the top of this put up, you’ll perceive why the Swift workforce determined to alter the closures that you simply go to duties, continuations, and activity teams to be sending
as a substitute of @Sendable
.
For those who’re not absolutely updated on Sendable
, I extremely advocate that you simply take a look at my put up on Sendable
and @Sendable
closures. On this put up, it is most related so that you can perceive the @Sendable
closures half as a result of we will be a comparability between a @Sendable
closure and a sending
argument.
Understanding the issue that’s solved by sending
In Swift 5, we did not have the sending
key phrase. That meant that if we needed to go a closure or a price from one place to a different safely, we’d do this with the sendable
annotation. So, for instance, Job
would have been outlined slightly bit like this in Swift 5.
public init(
precedence: TaskPriority? = nil,
operation: @Sendable @escaping () async -> Success
)
This initializer is copied from the Swift repository with some annotations stripped for simplicity.
Discover that the operation
argument takes a @Sendable
closure.
Taking a @Sendable
closure for one thing like a Job
signifies that that closure must be protected to name from some other duties or isolation context. In follow, which means no matter we do and seize within that closure should be protected, or in different phrases, it should be Sendable
.
So, a @Sendable
closure can basically solely seize Sendable
issues.
Which means the code beneath is just not protected in keeping with the Swift 5.10 compiler with strict concurrency warnings enabled.
Observe that operating the instance beneath in Xcode 16 with the Swift 6 compiler in Swift 5 mode is not going to throw any errors. That is as a result of
Job
has modified its operation to besending
as a substitute of@Sendable
at a language degree no matter language mode.So, even in Swift 5 language mode,
Job
takes asending
operation.
// The instance beneath requires the Swift 5 COMPILER to fail
// Utilizing the Swift 5 language mode is just not sufficient
func exampleFunc() {
let isNotSendable = MyClass()
Job {
// Seize of 'isNotSendable' with non-sendable kind 'MyClass' in a `@Sendable` closure
isNotSendable.rely += 1
}
}
If you wish to discover this compiler error in a venture that makes use of the Swift 6 compiler, you may outline your individual operate that takes a @Sendable
closure as a substitute of a Job
:
public func sendableClosure(
_ closure: @Sendable () -> Void
) {
closure()
}
For those who name that as a substitute of Job
, you’ll see the compiler error talked about earlier.
The compiler error is appropriate. We’re taking one thing that is not sendable and passing it right into a activity which in Swift 5 nonetheless took a @Sendable
closure.
The compiler does not like that as a result of the compiler says, “If it is a sendable closure, then it should be protected to name this from a number of isolation contexts, and if we’re capturing a non-sendable class, that isn’t going to work.”
This drawback is one thing that you’d run into sometimes, particularly with @Sendable
closures.
Our particular utilization right here is completely protected although. We’re creating an occasion of MyClass
within the operate that we’re making a activity or passing that occasion of MyClass
into the duty.
After which we’re by no means accessing it outdoors of the duty or after we make the duty anymore as a result of by the top of exampleFunc
this occasion is not retained outdoors of the Job
closure.
Due to this, there is no means that we will be passing isolation boundaries right here; No different place than our Job
has entry to our occasion anymore.
That’s the place sending
is available in…
Understanding sending arguments
In Swift 6, the workforce added a characteristic that enables us to inform the compiler that we intend to seize no matter non-sendable state we would obtain and do not wish to entry it elsewhere after capturing it.
This permits us to go non-sendable objects right into a closure that must be protected to name throughout isolation contexts.
In Swift 6, the code beneath is completely legitimate:
func exampleFunc() async {
let isNotSendable = MyClass()
Job {
isNotSendable.rely += 1
}
}
That’s as a result of Job
had its operation
modified from being @Sendable
to one thing that appears a bit as follows:
public init(
precedence: TaskPriority? = nil,
operation: sending @escaping () async -> Success
)
Once more, it is a simplified model of the particular initializer. The purpose is so that you can see how they changed @Sendable
with sending
.
As a result of the closure is now sending as a substitute of @sendable, the compiler can test that this occasion of MyClass that we’re passing into the duty is just not accessed or used after the duty captures it. So whereas the code above is legitimate, we are able to really write one thing that’s not legitimate.
For instance:
func exampleFunc() async {
let isNotSendable = MyClass()
// Worth of non-Sendable kind ... accessed after being transferred;
// later accesses might race
Job {
isNotSendable.rely += 1
}
// Entry can occur concurrently
print(isNotSendable.rely)
}
This transformation to the language permits us to go non-sendable state right into a Job
, which is one thing that you’re going to generally wish to do. It additionally makes certain that we’re not doing issues which can be doubtlessly unsafe, like accessing non-sendable state from a number of isolation contexts, which is what occurs within the instance above.
If you’re defining your individual capabilities that take closures that you simply wish to be protected to name from a number of isolation contexts, you’ll wish to mark them as sending
.
Defining your individual operate that takes a sending
closure seems to be as follows:
public func sendingClosure(
_ closure: sending () -> Void
) {
closure()
}
The sending
key phrase is added as a prefix to the closure kind, just like the place @escaping
would usually go.
In Abstract
You most likely will not be defining your individual sending
closures or your individual capabilities that take sending
arguments incessantly. The Swift workforce has up to date the initializers for duties, indifferent duties, the continuation APIs, and the duty group APIs to take sending
closures as a substitute of @Sendable
closures. Due to this, you may discover that Swift 6 means that you can do sure issues that Swift 5 would not let you do with strict concurrency enabled.
I believe it’s actually cool to know and perceive how sending
and @Sendable
work.
I extremely advocate that you simply experiment with the examples on this weblog put up by defining your individual sending
and @Sendable
closures and seeing how every may be known as and how one can name them from a number of duties. It is also value exploring how and when every choices stops working so that you’re conscious of their limitations.