Skip to content

Add discussion of ~Protocol syntax [SE-0390] #351

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
May 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 9 additions & 36 deletions TSPL.docc/LanguageGuide/Concurrency.md
Original file line number Diff line number Diff line change
Expand Up @@ -1324,52 +1324,25 @@ struct TemperatureReading {
-->

To explicitly mark a type as not being sendable,
overriding an implicit conformance to the `Sendable` protocol,
use an extension:
write `~Sendable` after the type:

```swift
struct FileDescriptor {
let rawValue: CInt
struct FileDescriptor: ~Sendable {
let rawValue: Int
}

@available(*, unavailable)
extension FileDescriptor: Sendable { }
```

<!--
The example above is abbreviated from a Swift System API.
The example above is based on a Swift System API.
https://github.com/apple/swift-system/blob/main/Sources/System/FileDescriptor.swift
-->

The code above shows part of a wrapper around POSIX file descriptors.
Even though interface for file descriptors uses integers
to identify and interact with open files,
and integer values are sendable,
a file descriptor isn't safe to send across concurrency domains.

<!--
- test: `suppressing-implied-sendable-conformance`

-> struct FileDescriptor {
-> let rawValue: CInt
-> }

-> @available(*, unavailable)
-> extension FileDescriptor: Sendable { }
>> let nonsendable: Sendable = FileDescriptor(rawValue: 10)
!$ warning: conformance of 'FileDescriptor' to 'Sendable' is unavailable; this is an error in Swift 6
!! let nonsendable: Sendable = FileDescriptor(rawValue: 10)
!! ^
!$ note: conformance of 'FileDescriptor' to 'Sendable' has been explicitly marked unavailable here
!! extension FileDescriptor: Sendable { }
!! ^
See also this PR that adds Sendable conformance to FileDescriptor:
https://github.com/apple/swift-system/pull/112
-->

In the code above,
the `FileDescriptor` is a structure
that meets the criteria to be implicitly sendable.
However, the extension makes its conformance to `Sendable` unavailable,
preventing the type from being sendable.
For more information about
suppressing an implicit conformance to a protocol,
see <doc:Protocols#Implicit-Conformance-to-a-Protocol>.

<!--
OUTLINE
Expand Down
73 changes: 72 additions & 1 deletion TSPL.docc/LanguageGuide/Generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ or indeed an array for any other type that can be created in Swift.
Similarly, you can create a dictionary to store values of any specified type,
and there are no limitations on what that type can be.

## The Problem That Generics Solve
## The Problem that Generics Solve

Here's a standard, nongeneric function called `swapTwoInts(_:_:)`,
which swaps two `Int` values:
Expand Down Expand Up @@ -1955,6 +1955,77 @@ Taken together, these constraints mean that
the value passed for the `indices` parameter
is a sequence of integers.

## Implicit Constraints

In addition to constraints you write explicitly,
many places in your generic code also implicitly require
conformance to some very common protocols like [`Copyable`][].
<!-- When SE-0446 is implemented, add Escapable above. -->
These generic constraints that you don't have to write
are known as *implicit constraints*.
For example, both of the following function declarations
require `MyType` to be copyable:

[`Copyable`]: https://developer.apple.com/documentation/swift/copyable

```swift
function someFunction<MyType> { ... }
function someFunction<MyType: Copyable> { ... }
```

Both declarations of `someFunction()` in the code above
require the generic type parameter `MyType` to be copyable.
In the first version, the constraint is implicit;
the second version lists the explicitly.
In most code,
types also implicitly conform to these common protocols.
For more information,
see <doc:Protocols#Implicit-Conformance-to-a-Protocol>.

Because most types in Swift conform to these protocols,
writing them almost everywhere would be repetitive.
Instead, by marking only the exceptions,
your call out the places that omit a common constraint.
To suppress an implicit constraint,
write the protocol name with a tilde (`~`) in front of it.
You can read `~Copyable` as "maybe copyable" ---
this suppressed constraint allows
both copyable and noncopyable types in this position.
Note that `~Copyable` doesn't *require* the type to be noncopyable.
For example:

```swift
func f<MyType>(x: inout MyType) {
let x1 = x // The value of x1 is a copy of x's value.
let x2 = x // The value of x2 is a copy of x's value.
}

func g<AnotherType: ~Copyable>(y: inout AnotherType) {
let y1 = y // The assignment consumes y's value.
let y2 = y // Error: Value consumed more than once.
}
```

In the code above,
the function `f()` implicitly requires `MyType` to be copyable.
Within the function body,
the value of `x` is copied to `x1` and `x2` in the assignment.
In contrast, `g()` suppresses the implicit constraint on `AnotherType`,
which allows you to pass either a copyable or noncopyable value.
Within the function body,
you can't copy the value of `y`
because `AnotherType` might be noncopyable.
Assignment consumes the value of `y`
and it's an error to consume that value more than once.
Noncopyable values like `y`
must be passed as in-out, borrowing, or consuming parameters ---
for more information,
see <doc:Declarations#Borrowing-and-Consuming-Parameters>.

For details about when generic code
includes an implicit constraint to a given protocol,
see the reference for that protocol.

<!--
TODO: Generic Enumerations
--------------------------
Expand Down
2 changes: 1 addition & 1 deletion TSPL.docc/LanguageGuide/OpaqueTypes.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Boxed protocol types don't preserve type identity ---
the value's specific type isn't known until runtime,
and it can change over time as different values are stored.

## The Problem That Opaque Types Solve
## The Problem that Opaque Types Solve

For example,
suppose you're writing a module that draws ASCII art shapes.
Expand Down
149 changes: 149 additions & 0 deletions TSPL.docc/LanguageGuide/Protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,65 @@ a nonfailable initializer or an implicitly unwrapped failable initializer.
```
-->

## Protocols that Have Only Semantic Requirements

All of the example protocols above require some methods or properties,
but a protocol declaration doesn't have to include any requirements.
You can also use a protocol to describe *semantic* requirements ---
that is, requirements about how values of those types behave
and about operations that they support.
<!--
Avoiding the term "marker protocol",
which more specifically refers to @_marker on a protocol.
-->
The Swift standard library defines several protocols
that don't have any required methods or properties:

- [`Sendable`][] for values that can be shared across concurrency domains,
as discussed in <doc:Concurrency#Sendable-Types>.
- [`Copyable`][] for values that Swift can copy
when you pass them to a function,
as discussed in <doc:Declarations#Borrowing-and-Consuming-Parameters>.
- [`BitwiseCopyable`][] for values that can be copied, bit-by-bit.

[`BitwiseCopyable`]: https://developer.apple.com/documentation/swift/bitwisecopyable
[`Copyable`]: https://developer.apple.com/documentation/swift/copyable
[`Sendable`]: https://developer.apple.com/documentation/swift/sendable

<!--
These link definitions are also used in the section below,
Implicit Conformance to a Protocol.
-->

For information about these protocols' requirements,
see the overview in their documentation.

You use the same syntax to adopt these protocols
as you do to adopt other protocols.
The only difference is that you don't include
method or property declarations that implement the protocol's requirements.
For example:

```swift
struct MyStruct: Copyable {
var counter = 12
}

extension MyStruct: BitwiseCopyable { }
```

The code above defines a new structure.
Because `Copyable` has only semantic requirements,
there isn't any code in the structure declaration to adopt the protocol.
Similarly, because `BitwiseCopyable` has only semantic requirements,
the extension that adopts that protocol has an empty body.

You usually don't need to write conformance to these protocols ---
instead, Swift implicitly adds the conformance for you,
as described in <doc:Protocols#Implicit-Conformance-to-a-Protocol>.

<!-- TODO: Mention why you might define your own empty protocols. -->

## Protocols as Types

Protocols don't actually implement any functionality themselves.
Expand Down Expand Up @@ -1368,6 +1427,96 @@ for level in levels.sorted() {
```
-->

## Implicit Conformance to a Protocol

Some protocols are so common that you would write them
almost every time you declare a new type.
For the following protocols,
Swift automatically infers the conformance
when you define a type that implements the protocol's requirements,
so you don't have to write them yourself:

- [`Copyable`][]
- [`Sendable`][]
- [`BitwiseCopyable`][]

<!--
The definitions for the links in this list
are in the section above, Protocols That Have Semantic Requirements.
-->

You can still write the conformance explicitly,
but it doesn't change how your code behaves.
To suppress an implicit conformance,
write a tilde (`~`) before the protocol name in the conformance list:

```swift
struct FileDescriptor: ~Sendable {
let rawValue: Int
}
```

<!--
The example above is based on a Swift System API.
https://github.com/apple/swift-system/blob/main/Sources/System/FileDescriptor.swift

See also this PR that adds Sendable conformance to FileDescriptor:
https://github.com/apple/swift-system/pull/112
-->

The code above shows part of a wrapper around POSIX file descriptors.
The `FileDescriptor` structure
satisfies all of the requirements of the `Sendable` protocol,
which normally makes it sendable.
However,
writing `~Sendable` suppresses this implicit conformance.
Even though file descriptors use integers
to identify and interact with open files,
and integer values are sendable,
making it nonsendable can help avoid certain kinds of bugs.

Another way to suppress implicit conformance
is with an extension that you mark as unavailable:

```swift
@available(*, unavailable)
extension FileDescriptor Sendable { }
```

<!--
- test: `suppressing-implied-sendable-conformance`

-> struct FileDescriptor {
-> let rawValue: CInt
-> }

-> @available(*, unavailable)
-> extension FileDescriptor: Sendable { }
>> let nonsendable: Sendable = FileDescriptor(rawValue: 10)
!$ warning: conformance of 'FileDescriptor' to 'Sendable' is unavailable; this is an error in Swift 6
!! let nonsendable: Sendable = FileDescriptor(rawValue: 10)
!! ^
!$ note: conformance of 'FileDescriptor' to 'Sendable' has been explicitly marked unavailable here
!! extension FileDescriptor: Sendable { }
!! ^
-->

When you write `~Sendable` in one place in your code,
as in the previous example,
code elsewhere in your program can still
extend the `FileDescriptor` type to add `Sendable` conformance.
In contrast,
the unavailable extension in this example
suppresses the implicit conformance to `Sendable`
and also prevents any extensions elsewhere in your code
from adding `Sendable` conformance to the type.

> Note:
> In addition to the protocols discussed above,
> distributed actors implicitly conform to the [`Codable`][] protocol.

[`Codable`]: https://developer.apple.com/documentation/swift/codable

## Collections of Protocol Types

A protocol can be used as the type to be stored in
Expand Down
2 changes: 2 additions & 0 deletions TSPL.docc/ReferenceManual/Declarations.md
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,8 @@ if you want more specific control,
you can apply the `borrowing` or `consuming` parameter modifier.
In this case,
use `copy` to explicitly mark copy operations.
In addition,
values of a noncopyable type must be passed as either borrowing or consuming.

Regardless of whether you use the default rules,
Swift guarantees that object lifetime and
Expand Down