Skip to content

Commit 3312a93

Browse files
rjmccallbeccadax
authored andcommitted
Revise SE-0430 to adopt sending in UnsafeContinuation
1 parent 626048e commit 3312a93

File tree

1 file changed

+66
-4
lines changed

1 file changed

+66
-4
lines changed

proposals/0430-transferring-parameters-and-results.md

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Review Manager: [Becca Royal-Gordon](https://github.com/beccadax)
66
* Status: **Implemented (Swift 6.0)**
77
* Previous Proposal: [SE-0414: Region-based isolation](/proposals/0414-region-based-isolation.md)
8+
* Previous Revisions: [1](https://github.com/apple/swift-evolution/blob/87943205551af43682ef50260816f3ff2ef9b7ea/proposals/0430-transferring-parameters-and-results.md) [2](https://github.com/apple/swift-evolution/blob/4dded8ed382b526a5a301c225a1d45018f8d556b/proposals/0430-transferring-parameters-and-results.md)
89
* Review: ([pitch](https://forums.swift.org/t/pitch-transferring-isolation-regions-of-parameter-and-result-values/70240)) ([first review](https://forums.swift.org/t/se-0430-transferring-isolation-regions-of-parameter-and-result-values/70830)) ([returned for revision](https://forums.swift.org/t/returned-for-revision-se-0430-transferring-isolation-regions-of-parameter-and-result-values/71297)) ([second review](https://forums.swift.org/t/se-0430-second-review-sendable-parameter-and-result-values/71685)) ([acceptance with modifications](https://forums.swift.org/t/accepted-with-modifications-se-0430-second-review-sendable-parameter-and-result-values/71850))
910

1011

@@ -404,17 +405,15 @@ so there is no way to change the default ownership convention of a
404405
### Adoption in the Concurrency library
405406

406407
There are several APIs in the concurrency library that send a parameter across
407-
isolation boundaries and don't need the full guarnatees of `Sendable`. These
408+
isolation boundaries and don't need the full guarantees of `Sendable`. These
408409
APIs will instead adopt `sending` parameters:
409410

410411
* `CheckedContinuation.resume(returning:)`
412+
* `UnsafeContinuation.resume(returning:)`
411413
* `Async{Throwing}Stream.Continuation.yield(_:)`
412414
* `Async{Throwing}Stream.Continuation.yield(with:)`
413415
* The `Task` creation APIs
414416

415-
Note that this list does not include `UnsafeContinuation.resume(returning:)`,
416-
because `UnsafeContinuation` deliberately opts out of correctness checking.
417-
418417
## Source compatibility
419418

420419
In the Swift 5 language mode, `sending` diagnostics are suppressed under
@@ -481,3 +480,66 @@ It was also suggested that perhaps instead of renaming `transferring` to
481480
authors since it runs into the same problem as `transferring` namely that it is
482481
suggesting that the value is actively being moved to another isolation domain,
483482
when we are expressing a latent capability of the value.
483+
484+
### Exclude `UnsafeContinuation`
485+
486+
An earlier version of this proposal excluded
487+
`UnsafeContinuation.resume(returning:)` from the list of standard library
488+
APIs that adopt `sending`. This meant that `UnsafeContinuation` didn't
489+
require either the return type to be `Sendable` or the return value to be
490+
`sending`. Since `UnsafeContinuation` is unconditionally `Sendable`, this
491+
effectively made it a major hole in sendability checking.
492+
493+
This was an intentional choice. The reasoning was that `UnsafeContinuation`
494+
was already an explicitly unsafe type, and so it's not illogical for it
495+
to also work as an unsafe opt-out from sendability checks. There are some
496+
uses of continuations that do need an opt-out like this. For example, it
497+
is not uncommon for a continuation to be resumed from a context that's
498+
isolated to the same actor as the context that called `withUnsafeContinuation`.
499+
In this situation, the data flow through the continuation is essentially
500+
internal to the actor. This means there's no need for any sendability
501+
restrictions; both `sending` and `Sendable` would be over-conservative.
502+
503+
However, the nature of the unsafety introduced by this exclusion is very
504+
different from the normal unsafety of an `UnsafeContinuation`.
505+
Continuations must be resumed exactly once; that's the condition that
506+
`CheckedContinuation` checks. If a programmer can prove that
507+
they will do that to their own satisfaction, they should be able to use
508+
`UnsafeContinuation` instead of `CheckedContinuation` in full confidence.
509+
Making `UnsafeContinuation` *also* a potential source of concurrency-safety
510+
holes is likely to be surprising to programmers.
511+
512+
Conversely, if a programmer needs to opt out of sendability checks but
513+
is *not* confident about how many times their continuation will be
514+
resumed --- for example, if it's resumed from an arbitrary callback ---
515+
forcing them to adopt `UnsafeContinuation` in order to achieve their
516+
goal is actively undesirable.
517+
518+
Not requiring `sending` in `UnsafeContinuation` also makes the high-level
519+
interfaces of `UnsafeContinuation` and `CheckedContinuation` inconsistent.
520+
This means that programmers cannot always easily move from an unsafe to a
521+
checked continuation. That is a common need, for example when fixing
522+
a bug and trying to prove that the unsafe continuation is not implicated.
523+
524+
Swift has generally resisted adding new dimensions of unsafety to unsafe
525+
types this way. For example, `UnsafePointer` was originally specified as
526+
unconditionally `Sendable` in [SE-0302][], but that conformance was
527+
removed in [SE-0331][], and pointers are now unconditionally
528+
non-`Sendable`. The logic in both of those proposals closely parallels
529+
this one: at first, `UnsafePointer` was seen as an unsafe type that should
530+
not be burdened with partial safety checks, and then the community
531+
recognized that this was actually adding a new dimension of unsafety to
532+
how the type interacted with concurrency.
533+
534+
Finally, there is already a general unsafe opt-out from sendability
535+
checking: `nonisolated(unsafe)`. It is better for Swift to encourage
536+
consistent use of a single unsafe opt-out than to build *ad hoc*
537+
opt-outs into many different APIs, because it is much easier to find,
538+
recognize, and audit uses of the former.
539+
540+
For these reasons, `UnsafeContinuation.resume(returning:)` now requires
541+
its argument to be `sending`, and the result of `withUnsafeContinuation`
542+
is correspondingly now marked as `sending`.
543+
544+
[SE-0302]: https://github.com/apple/swift-evolution/blob/main/proposals/0302-concurrent-value-and-concurrent-closures.md
545+
[SE-0331]: https://github.com/apple/swift-evolution/blob/main/proposals/0331-remove-sendable-from-unsafepointer.md

0 commit comments

Comments
 (0)