Skip to content
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

Add a concurrency + library evolution documentation. #146

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
211 changes: 211 additions & 0 deletions Guide.docc/LibraryEvolution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# Library Evolution

Annotate library APIs for concurrency while preserving source and ABI
compatibility.

Concurrency annotations such as `@MainActor` and `@Sendable` can impact source
and ABI compatibility that library authors should be aware of when annotating
existing APIs.

## Preconcurrency annotations

The `@preconcurrency` attribute can be used directly on library APIs to
stage in new concurrency requirements that are checked at compile time
without breaking source or ABI compatibility for clients:

```swift
@preconcurrency @MainActor
struct S { ... }

@preconcurrency
public func performConcurrently(
completion: @escaping @Sendable () -> Void
) { ... }
```

Clients do not need to use a `@preconcurrency import` for the new errors
to be downgraded. If the clients build with minimal concurrency checking,
errors from `@preconcurrency` APIs will be suppressed. If the clients build
with complete concurrency checking or the Swift 6 language mode, the errors
will be downgraded to warnings.

For ABI compatibility, `@preconcurrency` will mangle symbol names without any
concurrency annotations. If an API was introduced with some concurrency
annotations, and is later updated to include additional concurrency
annotations, then applying `@preconcurrency` is not sufficient for preserving
mangling. `@_silgen_name` can be used in cases where you need more precise
control over mangling concurrency annotations.

Note that all APIs imported from C, C++, and Objective-C are automatically
considered `@preconcurrency`. Concurrency attributes can always be applied
to these APIs using `__attribute__((__swift_attr__("<attribute name>")))`
without breaking source or ABI compatibility.

## Sendable

### Conformances on concrete types

Adding a `Sendable` conformance to a concrete type, including conditional
conformances, is typically a source compatible change in practice:

```diff
-public struct S
+public struct S: Sendable
```

Like any other conformance, adding a conformance to `Sendable` can change
overload resolution if the concrete type satisfies more specialized
requirements. However, it's unlikely that an API which overloads on a
`Sendable` conformance would change type inference in a way that breaks
source compatibility or program behavior.

Adding a `Sendable` conformance to a concrete type, and not one of its type
parameters, is always an ABI compatible change.

### Generic requirements

Adding a `Sendable` conformance requirement to a generic type or function is
a source incompatible change, because it places a restriction on generic
arguments passed by the client:

```diff
-public func generic<T>
+public func generic<T> where T: Sendable
```

Apply `@preconcurrency` to the type or function declaration to downgrade
requirement failures to warnings and preserve ABI:

```swift
@preconcurrency
public func generic<T> where T: Sendable { ... }
```

### Function types

Like generic requirements, adding `@Sendable` to a function type is a
source and ABI incompatible change:

```diff
-public func performConcurrently(completion: @escaping () -> Void)
+public func performConcurrently(completion: @escaping @Sendable () -> Void)
```

Apply `@preconcurrency` to the enclosing function declaration to downgrade
requirement failures to warnings and preserve ABI:

```swift
@preconcurrency
public func performConcurrently(completion: @escaping @Sendable () -> Void)
```

## Main actor annotations

### Protocols and types

Adding `@MainActor` annotations to protocols or type declarations is a source
and ABI incompatible change:

```diff
-public protocol P
+@MainActor public protocol P

-public class C
+@MainActor public class C
```

Adding `@MainActor` to protocols and type declarations has a wider impact than
other concurrency annotations because the `@MainActor` annotation can be
inferred throughout client code, including protocol conformances, subclasses,
and extension methods.

Applying `@preconcurrency` to the protocol or type declaration will downgrade
actor isolation errors based on the concurrency checking level. However,
`@preconcurrency` is not sufficient for preserving ABI compatibility for
clients in cases where the `@preconcurrency @MainActor` annotation can be
inferred on other declarations in client code. For example, consider the
following API in a client library:

```swift
extension P {
public func onChange(action: @escaping @Sendable () -> Void)
}
```

If `P` is retroactively annotated with `@preconcurrency @MainActor`, these
annotations will be inferred on the extension method. If an extension method is
also part of a library with ABI compatibility constraints, then
`@preconcurrency` will strip all concurrency related annotations from mangling.
This can be worked around in the client library either by applying the
appropriate isolation explicitly, such as:

```swift
extension P {
nonisolated public func onChange(action: @escaping @Sendable () -> Void)
}
```

Language affordances for precise control over the ABI of a declaration are
[under development](https://forums.swift.org/t/pitch-controlling-the-abi-of-a-declaration/75123).

### Function declarations and types

Adding `@MainActor` to a function declaration or a function type is a
source and ABI incompatible change:

```diff
-public func runOnMain()
+@MainActor public func runOnMain()

-public func performConcurrently(completion: @escaping () -> Void)
+public func performConcurrently(completion: @escaping @MainActor () -> Void)
```

Apply `@preconcurrency` to the enclosing function declaration to downgrade
requirement failures to warnings and preserve ABI:

```swift
@preconcurrency @MainActor
public func runOnMain() { ... }

@preconcurrency
public func performConcurrently(completion: @escaping @MainActor () -> Void) { ... }
```

## sending parameters and results

Adding `sending` to a result lifts restrictions in client code, and is
always a source and ABI compatible change:

```diff
-public func getValue() -> NotSendable
+public func getValue() -> sending NotSendable
```

However, adding `sending` to a parameter is more restrictive at the caller:

```diff
-public func takeValue(_: NotSendable)
+public func takeValue(_: sending NotSendable)
```

Adding `sending` to a parameter also changes name mangling, so any adoption
must preserve the mangling using `@_silgen_name`. Adopting `sending` in
parameter position must preserve the ownership convention of parameters. No
additional annotation is necessary if the parameter already has an explicit
ownership modifier. For all functions except initializers, use
`__shared sending` to preserve the ownership convention:

```swift
public func takeValue(_: __shared sending NotSendable)
```

For initializers, `sending` preserves the default ownership convention, so it's not
necessary to specify an ownership modifier when adopting `sending` on initializer
parameters:

```swift
public class C {
public init(ns: sending NotSendable)
}
```
1 change: 1 addition & 0 deletions Guide.docc/MigrationGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ For more information, see the [contributing][] document.
- <doc:CommonProblems>
- <doc:IncrementalAdoption>
- <doc:SourceCompatibility>
- <doc:LibraryEvolution>

### Swift Concurrency in Depth

Expand Down