SE-0518
~Sendable Conformance Syntax
Make intentional non-Sendable APIs visible at the declaration site.
Mental model: the tilde crosses out the default Sendable promise.
Why it exists
Silence was ambiguous
Leaving off Sendable did not tell readers whether a type was forgotten, intentionally non-Sendable, or only incidentally safe today because its current members happen to be Sendable.
For public APIs, authors may want to say "do not pass this across concurrency domains" early, so clients do not build around a guarantee the library never meant to make.
Old opt-out hurt subclasses
Making Sendable unavailable could express the intent, but for classes it also blocked safe subclasses from adopting Sendable later.
The whole hierarchy could get trapped by the base declaration.
~Sendable makes the opt-out explicit
Write : ~Sendable on a nominal type declaration to say the type is intentionally non-Sendable.
It lives right beside the rest of the type's conformance list.
Clearer APIs, cleaner migration
The language gets a real source-level opt-out that works with explicit-sendable checking and lets each subclass stay honest about its own guarantees.
Safe subclasses may opt in later; unsafe ones can remain non-Sendable.
THE CORE IDEA
The proposal turns a missing conformance into an explicit, reviewable decision.
// Before public struct JobResult { var value: NonSendable } // Did the author forget Sendable? // Or reject it on purpose? // The declaration cannot tell you.
// After public struct JobResult: ~Sendable { var value: NonSendable } // Intent is documented in the type itself. // Works beside other conformances too. struct Tagged: Equatable, ~Sendable { }
CLASS HIERARCHY MEMORY TRICK
Old workaround shuts the whole tree. ~Sendable only marks the base as intentionally non-Sendable.
@available(*, unavailable)
Blocks Sendable for every subclass too.
Cannot add Sendable even if its design is sound.
The whole branch inherits the same closed door.
Even a thread-safe subclass cannot opt back in.
Base: ~Sendable
The base is explicit, but subclasses still choose.
No pressure to promise what it cannot guarantee.
The base stays explicit while each subclass tells the truth about itself.
WHERE IT FITS
Only on nominal type declarations. It is not a general-purpose generic or protocol marker.
struct Cache: ~Sendable { } enum Token: ~Sendable { } class Base: ~Sendable { } struct Box: Equatable, ~Sendable { } extension Container: Sendable where T: Sendable { } // regular conditional Sendable conformance still works
extension Cache: ~Sendable { } protocol P: ~Sendable { } struct Holder<T: ~Sendable> { } actor Worker: ~Sendable { } struct S: ~Sendable, Sendable { } // actors are already Sendable; no double signal allowed
FAST FACTS
What to remember when you see the syntax in a review or codebase.
Sendable has no runtime representation, so is / as? Sendable casts are impossible.
This audit pushes API authors to spell out the Sendable story instead of relying on silence or inference. In the checked 6.4-dev nightly, the minimal declaration-side warning switch was -Wwarning ExplicitSendable; add -enable-experimental-feature TildeSendable only if you want to write ~Sendable, and treat -require-explicit-sendable as a recognized older switch that was not needed to surface the warning in this setup.
~Sendable does not block conditional conformance. A type can stay explicitly non-Sendable by default and still add Sendable later when generic constraints such as T: Sendable make that promise valid.
You cannot write ~Sendable on something that is already Sendable through another rule, such as actors, global-actor-isolated types, Sendable protocols, or Sendable superclasses.
Public APIs benefit most because readers need to know your intent.
Useful mental model: like ~Copyable, ~Escapable, and ~BitwiseCopyable, the tilde spells an explicit opt-out. The semantics differ, but the source-level pattern is similar.
TRY IT TODAY
Use a nightly main-development snapshot toolchain. One switch lets you actually write ~Sendable; another surfaces the explicit-sendable diagnostics for silent public types.
// Other Swift Flags (separate entries) // try ~Sendable -enable-experimental-feature TildeSendable // show explicit-sendable diagnostics -Wwarning ExplicitSendable // also recognized in the checked nightly // -require-explicit-sendable
// Package.swift -> target swiftSettings .target( name: "MyModule", swiftSettings: [ .unsafeFlags([ // try ~Sendable "-enable-experimental-feature", "TildeSendable", // show explicit-sendable diagnostics "-Wwarning", "ExplicitSendable", // also recognized in the checked nightly // "-require-explicit-sendable", ]) ] )
TO BE SENDABLE, OR NOT TO BE SENDABLE?
Interpretive question, not a rule: should an app try to make almost everything Sendable by default?
Why teams like it
Concurrency mistakes surface early instead of turning into accidental shared mutable state.
A Sendable-first codebase makes it simpler to move values across tasks and actors later.
It nudges stateful or thread-bound parts into smaller, more explicit isolation zones.
Why teams resist it
Not every type is meant to cross concurrency domains, especially UI and local mutable models.
Developers may reach for wrappers, locks, or @unchecked just to satisfy the checker.
A strict Sendable-everywhere mindset can make simple app code feel more ceremonial than useful.
Balanced takeaway
~Sendable exists because making intent explicit is often better than pretending every type should participate in cross-concurrency transfer. Strong defaults help, but forcing them everywhere can blur design truth.
REFERENCES
TildeSendable
Compiler docs - explicit-sendable annotations
Diagnostic page for the ExplicitSendable warning group
SE-0390 - ~Copyable
Related tilde syntax background
SE-0302 - Sendable and @Sendable
Original Sendable context