8.7 C
Canberra
Tuesday, October 28, 2025

Sending vs Sendable in Swift – Donny Wals


With Swift 6, we now have a completely new model of the language that has all types of information race protections built-in. Most of those protections had been round with Swift 5 in a technique 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 could possibly get away with sure issues the place in Swift 6 these at the moment are compiler errors.

Swift 6 additionally introduces a bunch of recent options, considered one of these is the sending key phrase. Sending intently pertains to Sendable, however they’re fairly completely different by way of why they’re used, what they will do, and which issues they have a tendency to unravel.

On this submit, I want to discover the similarities and variations between Sendable and sending. By the top of this submit, you’ll perceive why the Swift workforce determined to alter the closures that you just move to duties, continuations, and process teams to be sending as an alternative of @Sendable.

Should you’re not absolutely updated on Sendable, I extremely suggest that you just take a look at my submit on Sendable and @Sendable closures. On this submit, 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 move a closure or a price from one place to a different safely, we’d try this with the sendable annotation. So, for instance, Process 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 Process implies that that closure needs to be protected to name from another duties or isolation context. In follow, which means no matter we do and seize inside that closure have to be protected, or in different phrases, it have to be Sendable.

So, a @Sendable closure can basically solely seize Sendable issues.

Which means that the code under just isn’t protected in line with the Swift 5.10 compiler with strict concurrency warnings enabled.

Word that operating the instance under 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 Process has modified its operation to be sending as an alternative of @Sendable at a language degree no matter language mode.

So, even in Swift 5 language mode, Process takes a sending operation.

// The instance under requires the Swift 5 COMPILER to fail
// Utilizing the Swift 5 language mode just isn't sufficient
func exampleFunc() {
  let isNotSendable = MyClass()

  Process {
      // Seize of 'isNotSendable' with non-sendable sort 'MyClass' in a `@Sendable` closure
    isNotSendable.rely += 1
  }
}

If you wish to discover this compiler error in a challenge that makes use of the Swift 6 compiler, you may outline your personal operate that takes a @Sendable closure as an alternative of a Process:

public func sendableClosure(
  _ closure: @Sendable () -> Void
  ) {
  closure()
}

Should you name that as an alternative of Process, 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 process which in Swift 5 nonetheless took a @Sendable closure.

The compiler does not like that as a result of the compiler says, “If this can be a sendable closure, then it have to 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 inside the operate that we’re making a process or passing that occasion of MyClass into the duty.

After which we’re by no means accessing it exterior of the duty or after we make the duty anymore as a result of by the top of exampleFunc this occasion is not retained exterior of the Process closure.

Due to this, there is not any means that we will be passing isolation boundaries right here; No different place than our Process has entry to our occasion anymore.

That’s the place sending is available in…

Understanding sending arguments

In Swift 6, the workforce added a function that enables us to inform the compiler that we intend to seize no matter non-sendable state we would obtain and do not need to entry it elsewhere after capturing it.

This permits us to move non-sendable objects right into a closure that must be protected to name throughout isolation contexts.

In Swift 6, the code under is completely legitimate:

func exampleFunc() async {
  let isNotSendable = MyClass()

  Process {
    isNotSendable.rely += 1
  }
}

That’s as a result of Process 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, this can be 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 an alternative of @sendable, the compiler can test that this occasion of MyClass that we’re passing into the duty just isn’t accessed or used after the duty captures it. So whereas the code above is legitimate, we will truly write one thing that’s not legitimate.

For instance:

func exampleFunc() async {
  let isNotSendable = MyClass()

  // Worth of non-Sendable sort ... accessed after being transferred; 
  // later accesses might race
  Process {
    isNotSendable.rely += 1
  }

  // Entry can occur concurrently
  print(isNotSendable.rely)
} 

This modification to the language permits us to move non-sendable state right into a Process, which is one thing that you will generally need to do. It additionally makes certain that we’re not doing issues which are probably unsafe, like accessing non-sendable state from a number of isolation contexts, which is what occurs within the instance above.

In case you are defining your personal capabilities that take closures that you just need to be protected to name from a number of isolation contexts, you’ll need to mark them as sending.

Defining your personal operate that takes a sending closure appears as follows:

public func sendingClosure(
  _ closure: sending () -> Void
) {
  closure()
}

The sending key phrase is added as a prefix to the closure sort, just like the place @escaping would usually go.

In Abstract

You in all probability will not be defining your personal sending closures or your personal capabilities that take sending arguments ceaselessly. 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 an alternative of @Sendable closures. Due to this, you may discover that Swift 6 permits you to do sure issues that Swift 5 would not assist you to do with strict concurrency enabled.

I believe it’s actually cool to know and perceive how sending and @Sendable work.

I extremely suggest that you just experiment with the examples on this weblog submit by defining your personal sending and @Sendable closures and seeing how every will be known as and how one can name them from a number of duties. It is also price exploring how and when every choices stops working so that you’re conscious of their limitations.

Additional studying

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

[td_block_social_counter facebook="tagdiv" twitter="tagdivofficial" youtube="tagdiv" style="style8 td-social-boxed td-social-font-icons" tdc_css="eyJhbGwiOnsibWFyZ2luLWJvdHRvbSI6IjM4IiwiZGlzcGxheSI6IiJ9LCJwb3J0cmFpdCI6eyJtYXJnaW4tYm90dG9tIjoiMzAiLCJkaXNwbGF5IjoiIn0sInBvcnRyYWl0X21heF93aWR0aCI6MTAxOCwicG9ydHJhaXRfbWluX3dpZHRoIjo3Njh9" custom_title="Stay Connected" block_template_id="td_block_template_8" f_header_font_family="712" f_header_font_transform="uppercase" f_header_font_weight="500" f_header_font_size="17" border_color="#dd3333"]
- Advertisement -spot_img

Latest Articles