@@ -297,7 +297,7 @@ Together, these could require significant structural changes to address.
297
297
This may still be the right solution, but the side-effects should be carefully
298
298
considered first, even if only a small number of types are involved.
299
299
300
- #### Using Preconcurrency
300
+ #### Preconcurrency Conformance
301
301
302
302
Swift has a number of mechanisms to help you adopt concurrency incrementally
303
303
and interoperate with code that has not yet begun using concurrency at all.
@@ -391,3 +391,331 @@ struct CustomWindowStyle: Styler {
391
391
Here, a new type has been created that can satisfy the needed inheritance.
392
392
Incorporating will be easiest if the conformance is only used internally by
393
393
` WindowStyler ` .
394
+
395
+ ## Crossing Isolation Boundaries
396
+
397
+ Any value that needs to move from one isolation domain to another
398
+ must either be ` Sendable ` or must preserve mutually exclusive access.
399
+ Using values with types that do not satisfy these requirements in contexts
400
+ that require them is a very common problem.
401
+ And because libraries and frameworks may be updated to use Swift's
402
+ concurrency features, these issues can come up even when your code hasn't
403
+ changed.
404
+
405
+ ### Implicitly-Sendable Types
406
+
407
+ Many value types consist entirely of ` Sendable ` properties.
408
+ The compiler will treat types like this as implicitly ` Sendable ` , but _ only_
409
+ when they are non-public.
410
+
411
+ ``` swift
412
+ public struct ColorComponents {
413
+ public let red: Float
414
+ public let green: Float
415
+ public let blue: Float
416
+ }
417
+
418
+ @MainActor
419
+ func applyBackground (_ color : ColorComponents) {
420
+ }
421
+
422
+ func updateStyle (backgroundColor : ColorComponents) async {
423
+ await applyBackground (backgroundColor)
424
+ }
425
+ ```
426
+
427
+ A ` Sendable ` conformance is part of a type's public API contract,
428
+ and that is up to you to define.
429
+ Because ` ColorComponents ` is marked ` public ` it will not have an implicit
430
+ conformance to ` Sendable ` .
431
+ This will result in the following error:
432
+
433
+ ```
434
+ 6 |
435
+ 7 | func updateStyle(backgroundColor: ColorComponents) async {
436
+ 8 | await applyBackground(backgroundColor)
437
+ | |- error: sending 'backgroundColor' risks causing data races
438
+ | `- note: sending task-isolated 'backgroundColor' to main actor-isolated global function 'applyBackground' risks causing data races between main actor-isolated and task-isolated uses
439
+ 9 | }
440
+ 10 |
441
+ ```
442
+
443
+ A very straightforward solution is just to make the type's ` Sendable `
444
+ conformance explicit.
445
+
446
+ ``` swift
447
+ public struct ColorComponents : Sendable {
448
+ // ...
449
+ }
450
+ ```
451
+
452
+ Even when trivial, adding a ` Sendable ` conformance should always be
453
+ done with care.
454
+ Remember that ` Sendable ` is a guarantee of thread-safety, and part of a
455
+ type's API contract.
456
+ Removing the conformance is an API-breaking change.
457
+
458
+ ### Preconcurrency Import
459
+
460
+ Even if the type in another module is actually ` Sendable ` , it is not always
461
+ possible to modify its definition.
462
+ In this case, you can use a ` @preconcurrency import ` to suppress errors until
463
+ the library is updated.
464
+
465
+ ``` swift
466
+ // ColorComponents defined here
467
+ @preconcurrency import UnmigratedModule
468
+
469
+ func updateStyle (backgroundColor : ColorComponents) async {
470
+ // crossing an isolation domain here
471
+ await applyBackground (backgroundColor)
472
+ }
473
+ ```
474
+
475
+ With the addition of this ` @preconcurrency import ` ,
476
+ ` ColorComponents ` remains non-` Sendable ` .
477
+ However, the compiler's behavior will be altered.
478
+ When using the Swift 6 language mode, the produced here will be downgraded
479
+ to a warning.
480
+ The Swift 5 language mode will produce no diagnostics at all.
481
+
482
+ ### Latent Isolation
483
+
484
+ Sometimes the _ apparent_ need for a ` Sendable ` type can actually be the
485
+ symptom of a more fundamental isolation problem.
486
+ The only reason a type needs to be ` Sendable ` is to cross isolation boundaries.
487
+ If you can avoid crossing boundaries altogether, the result can
488
+ often be both simpler and a better reflection of the true nature of your
489
+ system.
490
+
491
+ ``` swift
492
+ @MainActor
493
+ func applyBackground (_ color : ColorComponents) {
494
+ }
495
+
496
+ func updateStyle (backgroundColor : ColorComponents) async {
497
+ await applyBackground (backgroundColor)
498
+ }
499
+ ```
500
+
501
+ The ` updateStyle(backgroundColor:) ` function is non-isolated.
502
+ This means that its non-` Sendable ` parameter is also non-isolated.
503
+ But, it is immediately crossing from this non-isolated domain to the
504
+ ` MainActor ` when ` applyBackground(_:) ` is called.
505
+
506
+ Since ` updateStyle(backgroundColor:) ` is working directly with
507
+ ` MainActor ` -isolated functions and non-` Sendable ` types,
508
+ just applying ` MainActor ` isolation may be more appropriate.
509
+
510
+ ``` swift
511
+ @MainActor
512
+ func updateStyle (backgroundColor : ColorComponents) async {
513
+ applyBackground (backgroundColor)
514
+ }
515
+ ```
516
+
517
+ Now, there is no longer an isolation boundary for the non-` Sendable ` type to
518
+ cross.
519
+ And in this case, not only does this resolve the problem, it also
520
+ removes the need for an asynchronous call.
521
+ Fixing latent isolation issues can also potentially make further API
522
+ simplification possible.
523
+
524
+ Lack of ` MainActor ` isolation like this is, by far, the most common form of
525
+ latent isolation.
526
+ It is also very common for developers to hesitate to use this as a solution.
527
+ It is completely normal for programs with a user interface to have a large
528
+ set of ` MainActor ` -isolated state.
529
+ Concerns around long-running _ synchronous_ work can often be addressed with
530
+ just a handful of targeted ` nonisolated ` functions.
531
+
532
+ ### Computed Value
533
+
534
+ Instead of trying to pass a non-` Sendable ` type across a boundary, it may be
535
+ possible to use a ` Sendable ` function that creates the needed values.
536
+
537
+ ``` swift
538
+ func updateStyle (backgroundColorProvider : @Sendable () -> ColorComponents) async {
539
+ await applyBackground (using : backgroundColorProvider)
540
+ }
541
+ ```
542
+
543
+ Here, it does not matter than ` ColorComponents ` is not ` Sendable ` .
544
+ By using ` @Sendable ` function that can compute the value, the lack of
545
+ sendability is side-stepped entirely.
546
+
547
+ ### Sendable Conformance
548
+
549
+ When encountering problems related to crossing isolation domains, a very
550
+ natural reaction is to just try to add a conformance to ` Sendable ` .
551
+ You can make a type ` Sendable ` in four ways.
552
+
553
+ #### Global Isolation
554
+
555
+ Adding global isolation to any type will make it implicitly ` Sendable ` .
556
+
557
+ ``` swift
558
+ @MainActor
559
+ public struct ColorComponents {
560
+ // ...
561
+ }
562
+ ```
563
+
564
+ By isolating this type to the ` MainActor ` , any accesses from other isolation domains
565
+ must be done asynchronously.
566
+ This makes it possible to safely pass instances around across domains.
567
+
568
+ #### Actors
569
+
570
+ Actors have an implicit ` Sendable ` conformance because their properties are
571
+ protected by actor isolation.
572
+
573
+ ``` swift
574
+ actor Style {
575
+ private var background: ColorComponents
576
+ }
577
+ ```
578
+
579
+ In addition to gaining a ` Sendable ` conformance, actors have their own
580
+ isolation domain.
581
+ This allows them to freely work with other non-` Sendable ` types internally.
582
+ This can be a major advantage, but does come with trade-offs.
583
+
584
+ Because an actor's isolated methods all must be asynchronous,
585
+ sites that access the type may now require an async context.
586
+ This alone is a reason to make such a change with care.
587
+ But further, data that is passed into or out of the actor may now itself
588
+ need to cross the new isolation boundary.
589
+ This can end up resulting in the need for yet more ` Sendable ` types.
590
+
591
+ #### Manual Synchronization
592
+
593
+ If you have a type that is already doing manual synchronization, you can
594
+ express this to the compiler by marking your ` Sendable ` conformance as
595
+ ` unchecked ` .
596
+
597
+ ``` swift
598
+ class Style : @unchecked Sendable {
599
+ private var background: ColorComponents
600
+ private let queue: DispatchQueue
601
+ }
602
+ ```
603
+
604
+ You should not feel compelled to remove use of queues, locks, or other
605
+ forms of manual synchronization to integrate with Swift's concurrency system.
606
+ However, most types are not inherently thread-safe.
607
+ As a general rule, if a type isn't already thread-safe, attempting to make
608
+ it ` Sendable ` should not be your first approach.
609
+ It is often much easier to try other techniques first, falling back to
610
+ manual synchronization only when truly necessary.
611
+
612
+ #### Sendable Reference Types
613
+
614
+ It is possible for reference types to be validated as ` Sendable ` without
615
+ the ` unchecked ` qualifier.
616
+ But, this can only be done under very narrow circumstances.
617
+
618
+ To allow a checked ` Sendable ` conformance a class:
619
+
620
+ - Must be ` final `
621
+ - Cannot inherit from another class other than ` NSObject `
622
+ - Cannot have any non-isolated mutable properties
623
+
624
+ ``` swift
625
+ public struct ColorComponents : Sendable {
626
+ // ...
627
+ }
628
+
629
+ final class Style : Sendable {
630
+ private let background: ColorComponents
631
+ }
632
+ ```
633
+
634
+ Sometimes, this is a sign of a struct in disguise.
635
+ But this can still be a useful technique when reference semantics need to be
636
+ preserved, or for types that are part of a mixed Swift/Objective-C code base.
637
+
638
+ #### Using Composition
639
+
640
+ You do not need to select one single technique for making a reference type
641
+ ` Sendable. `
642
+ One type can use many techniques internally.
643
+
644
+ ``` swift
645
+ final class Style : Sendable {
646
+ private nonisolated (unsafe) var background: ColorComponents
647
+ private let queue: DispatchQueue
648
+
649
+ @MainActor
650
+ private var foreground: ColorComponents
651
+ }
652
+ ```
653
+
654
+ The ` background ` property is protected by manual synchronization,
655
+ while the ` foreground ` property uses actor isolation.
656
+ Combining these two techniques results in a type that better describes its
657
+ internal semantics.
658
+ And by doing this, the type can now continue to take advantage of the
659
+ compiler's automated isolation checking.
660
+
661
+ ### Non-Isolated Initialization
662
+
663
+ Actor-isolated types can present a problem when they have to be initialized in
664
+ a non-isolated context.
665
+ This occurs frequently when the type is used in a default value expression or
666
+ as a property initializer.
667
+
668
+ > Note: These problems could also be a symptom of
669
+ [ latent isolation] ( #Latent-Isolation ) or an
670
+ [ under-specified protocol] ( #Under-Specified-Protocol ) .
671
+
672
+ Here the non-isolated ` Stylers ` type is making a call to a
673
+ ` MainActor ` -isolated initializer.
674
+
675
+ ``` swift
676
+ @MainActor
677
+ class WindowStyler {
678
+ init () {
679
+ }
680
+ }
681
+
682
+ struct Stylers {
683
+ static let window = WindowStyler ()
684
+ }
685
+ ```
686
+
687
+ This code results in the following error:
688
+
689
+ ```
690
+ 7 |
691
+ 8 | struct Stylers {
692
+ 9 | static let window = WindowStyler()
693
+ | `- error: main actor-isolated default value in a nonisolated context
694
+ 10 | }
695
+ 11 |
696
+ ```
697
+
698
+ Globally-isolated types sometimes don't actually need to reference any global
699
+ actor state in their initializers.
700
+ By making the ` init ` method ` nonisolated ` , it is free to be called from any
701
+ isolation domain.
702
+ This remains safe as the compiler still guarantees that any state that * is*
703
+ isolated will only be accessible from the ` MainActor ` .
704
+
705
+ ``` swift
706
+ @MainActor
707
+ class WindowStyler {
708
+ private var viewStyler = ViewStyler ()
709
+ private var primaryStyleName: String
710
+
711
+ nonisolated init (name : String ) {
712
+ self .primaryStyleName = name
713
+ // type is fully-initialized here
714
+ }
715
+ }
716
+ ```
717
+
718
+
719
+ All ` Sendable ` properties can still be safely accessed in this ` init ` method.
720
+ And while any non-` Sendable ` properties cannot,
721
+ they can still be initialized by using default expressions.
0 commit comments