Skip to content

Commit 1b86a3b

Browse files
authored
Merge pull request #2476 from rjmccall/se-0430-unsafecontinuation-sending
Revise SE-0430 to adopt `sending` in `UnsafeContinuation`
2 parents 626048e + 8aec082 commit 1b86a3b

File tree

1 file changed

+68
-5
lines changed

1 file changed

+68
-5
lines changed

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

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
* Proposal: [SE-0430](0430-transferring-parameters-and-results.md)
44
* Authors: [Michael Gottesman](https://github.com/gottesmm), [Holly Borla](https://github.com/hborla), [John McCall](https://github.com/rjmccall)
55
* Review Manager: [Becca Royal-Gordon](https://github.com/beccadax)
6-
* Status: **Implemented (Swift 6.0)**
6+
* Status: **Active Review (June 21 ... 28, 2024)**
7+
* Implementation: Implemented in Swift 6.0 except for amendment under review
78
* Previous Proposal: [SE-0414: Region-based isolation](/proposals/0414-region-based-isolation.md)
9+
* 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)
810
* 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))
911

1012

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

406408
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
409+
isolation boundaries and don't need the full guarantees of `Sendable`. These
408410
APIs will instead adopt `sending` parameters:
409411

410412
* `CheckedContinuation.resume(returning:)`
413+
* `UnsafeContinuation.resume(returning:)`
411414
* `Async{Throwing}Stream.Continuation.yield(_:)`
412415
* `Async{Throwing}Stream.Continuation.yield(with:)`
413416
* The `Task` creation APIs
414417

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

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

0 commit comments

Comments
 (0)