What is the difference between sending and consuming?
These solve two completely different problems and it’s easy to conflate them since they both sit in the parameter position and both are about “who owns this value.”
consuming — about local ownership/lifetime (SE-0377)
This is part of Swift’s ownership system, mostly relevant for performance-critical code and non-copyable types. It says: “this function takes ownership of the value; the caller can no longer use it afterward.”
func process(_ file: consuming FileHandle) {
// this function now owns `file`
// it can destroy it, store it, whatever — caller's copy is gone
}
let handle = FileHandle(...)
process(handle)
// using `handle` here is now an error (for non-copyable types)
// or silently makes a fresh copy (for copyable types, no real effect visible)
Functions that borrow noncopyable values make temporary use of the value and leave it valid for further use, whereas functions that consume a noncopyable value consume it and prevent its further use — the canonical example being closing a file handle. Its counterpart is borrowing (temporary, read-only access, value still valid after the call).
This is entirely about single-threaded memory/lifetime semantics — copies, destruction timing, avoiding retain/release overhead. It has nothing to do with concurrency by itself.
sending — about crossing actor/concurrency boundaries (SE-0430)
This says: “this value must be safe to hand off to a different isolation domain (actor, task) at this point,” even if the type itself isn’t Sendable.
func process(_ data: sending NonSendableThing) {
Task {
// safe to use `data` here on a different isolation domain,
// because `sending` guaranteed no one else holds a reference to it
useOnAnotherActor(data)
}
}
sending requires parameter and result values to be in a disconnected region at the function boundary — meaning nothing else in the program still references that value, so it’s provably safe to move across an actor boundary without a data race, even though its type doesn’t conform to Sendable. Once you pass a sending value in, the caller can’t use it anymore either (similar consumption-like restriction), but the purpose is concurrency safety, not memory-layout optimization.
Interestingly, the proposal authors originally considered calling it transferring to superficially match consuming/borrowing, but rejected that — sending doesn’t actively consume or borrow the value at the boundary the way ownership modifiers do; it just requires the value be in a disconnected region so it can be sent elsewhere.
Side-by-side
consuming | sending | |
|---|---|---|
| Problem domain | Ownership / memory lifetime | Concurrency / data-race safety |
| SE proposal | SE-0377 | SE-0430 |
| What it guarantees | Caller loses the value; function now owns it | Value has no other live references, so it’s safe to move to another isolation domain |
| Relevant even single-threaded? | Yes | No — meaningless outside concurrency |
Interacts with Sendable | No | Yes — lets non-Sendable types cross boundaries safely in specific cases |
Can they combine? Yes — you can have a consuming sending parameter, since they’re addressing orthogonal concerns (one about local reuse, one about cross-actor safety), though there are known rough edges, e.g. no way to express a consuming capture for a closure when passing a sending value into a non-escaping closure, requiring workarounds.
Rule of thumb: if you’re thinking about “can I still use this variable after passing it?” in a normal single-threaded sense, that’s consuming. If you’re thinking about “can I safely hand this off to a Task or another actor?” that’s sending.