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

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
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
69 changes: 69 additions & 0 deletions TSPL.docc/LanguageGuide/Generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -1955,6 +1955,75 @@ Taken together, these constraints mean that
the value passed for the `indices` parameter
is a sequence of integers.

## Implicit Constraints

In addition to the constraints you write explicitly,
many places in your code
also implicitly include an constraint
that types conform to some very common protocols
like [`Copyable`][].
<!-- When SE-0446 is implemented, add Escapable above -->
For information on when a protocol is implied,
see the reference for that protocol.

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

This constraint is implicit because
almost all types in Swift conform to these protocols,
so you specify only the exceptions.
For example, both of the following function declarations
require `MyType` to be 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 protocol.
For more information,
see <doc:Protocols#Implicit-Conformance-to-a-Protocol>.

To suppress an implicit constraint,
you 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>.

<!--
TODO: Generic Enumerations
--------------------------
Expand Down
153 changes: 153 additions & 0 deletions TSPL.docc/LanguageGuide/Protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,67 @@ a nonfailable initializer or an implicitly unwrapped failable initializer.
```
-->

## Protocols That Have Semantic Requirements
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All protocols have semantic requirements (we have said repeatedly that "protocols are not just bags of syntax"). Perhaps:

Suggested change
## Protocols That Have Semantic Requirements
## Protocols That Have Only Semantic Requirements


All of the example protocols above require some methods or properties,
but a protocol doesn't have to include any requirements.
You can also use a protocol to mark types
that satisfy some *semantic* requirements ---
requirements about how values of those types behave
and about operations that they support ---
not just requirements that you express in code.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
not just requirements that you express in code.
not just requirements that you express in code.

Same comment regarding language here.

<!--
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.
Likewise, 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>.

<!-- XXX TR: 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 +1429,98 @@ 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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... especially since I tried to suppress Codable conformance, but the compiler complains about Type 'Codable' (aka 'Decodable & Encodable') cannot be suppressed


```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

XXX SE-0390 uses the same example but ~Copyable -- is that better?
Comment on lines +1462 to +1468
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewers: Any opinion on this alternative version of the code example?

-->

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 would normally make 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