---
title: "SE-0476: Controlling the ABI of a function, initializer, property, or subscript"
framework: swift-evolution
role: article
path: swift-evolution/0476-abi-attr
---

# SE-0476: Controlling the ABI of a function, initializer, property, or subscript

* Proposal: [SE-0476](0476-abi-attr.md)
* Authors: [Becca Royal-Gordon](https://github.com/beccadax)
* Review Manager: [Holly Borla](https://github.com/hborla)
* Status: **Implemented (Swift 6.2)**
* Review: ([pitch](https://forums.swift.org/t/pitch-controlling-the-abi-of-a-declaration/75123)) ([review](https://forums.swift.org/t/se-0476-controlling-the-abi-of-a-function-initializer-property-or-subscript/79233)) ([acceptance](https://forums.swift.org/t/accepted-with-modifications-se-0476-controlling-the-abi-of-a-function-initializer-property-or-subscript/79644))

## Introduction

We propose introducing the `@abi` attribute, which provides an alternate version of the declaration used for name mangling. This feature would allow developers of ABI-stable libraries to make minor changes, such as changing the sendability of a parameter or renaming a declaration (so long as source compatibility is preserved in a backwards-deployable way), without requiring deep knowledge of compiler implementation details.

## Motivation

Maintainers of ABI-stable libraries sometimes need to update or correct existing declarations for various reasons:

1. To adopt new language features, like changing `@Sendable` to `sending`,    in an existing declaration.

2. To replace an existing declaration with a source-compatible but ABI-breaking    equivalent, like replacing a `rethrows` method with one using typed    `throws`.

3. To correct a mistake, like removing an unnecessary `@escaping` attribute or    adding a `Sendable` generic constraint.     4. To rename an API whose name is felt to be catastrophically confusing.

Many revisions will cause fundamental changes in how an API will be used at the machine code level that clients must account for; for instance, changing `<T>` to `<T: Hashable>` requires callers to generate code that will pass the witness table for `T`'s `Hashable` conformance. However, some features are designed to have little or no impact on the code generated by the caller. For example, these two declarations:

```swift // `T` must be `Sendable` func fn<T: Sendable>(_: T) {}

// `T` parameter must be `sending` func fn<T>(_: borrowing sending T) {}   // note: 'borrowing sending' is currently banned,                                         // pending a decision on whether it should have the                                         // meaning we want it to have here ```

Have identical parameter signatures at the IR level, with one pointer to the argument and another pointer to `T`'s value witness table, and use the same result type and calling convention too:

```text define hidden swiftcc void @"$s4main2fnyyxs8SendableRzlF"(ptr noalias %0, ptr %T)               ^~~~~~~~~~~~                               ^~~~~~~~~~~~~~~~~~~~~~~~ define hidden swiftcc void @"$s4main2fnyyxlF"(ptr noalias %0, ptr %T)               ^~~~~~~~~~~~                   ^~~~~~~~~~~~~~~~~~~~~~~~ ```

Other details, such as the parameter ownership conventions, also line up to make this work; suffice it to say, the function generated when you use `borrowing sending` is perfectly capable of handling the arguments passed by callers that think the parameter is `Sendable`. The only differences between them are the compile-time checks applied by the compiler and the part of their mangled names that indicates the feature being used:

```text define hidden swiftcc void @"$s4main2fnyyxs8SendableRzlF"(ptr noalias %0, ptr %T)                                          ^~~~~~~~~~~~~ 'T: Sendable' define hidden swiftcc void @"$s4main2fnyyxlF"(ptr noalias %0, ptr %T)                                          ^ 'T' ('sending' is not indicated by the mangled name) ```

Thus, if there was a way to tell the compiler to continue using the mangled name for `fn<T: Sendable>(_: T)`, a library designer could actually change the declaration to be treated like `fn<T>(_: borrowing sending T)` when compiling with the new version of the library *without* breaking ABI compatibility.

This is part of how the `@preconcurrency` attribute works. `@preconcurrency` has two effects: It instructs the type checker to permit Swift 5 code to use the declaration in ways that would violate the rules of certain concurrency annotations, and it causes those annotations to be omitted from the declaration's mangled name. That makes it perfect for retrofitting sendability checking onto APIs that were created before Swift Concurrency was introduced. However, it is designed specifically for that exact task, which makes it inflexible: It cannot be used to suppress some concurrency features but not others (for instance, to amend a mistake in one parameter without affecting other parts of the declaration), and it cannot be applied to adopt non-concurrency features which have the same property of being ABI-compatible except for a different mangled name.

For everything else, there's the compiler-internal `@_silgen_name` attribute. `@_silgen_name` is an internal hack that overrides name mangling at specific points in the compiler, replacing the mangled name with an arbitrary string. If you know the original mangled name, therefore, you can use this attribute to keep that name stable even if the declaration has evolved enough that it would normally use a different name. That makes `@_silgen_name` enormously flexible—it can be used to handle an arbitrary set of changes, and the standard library uses it extensively for this purpose. For example, when the standard library introduced a new `Collection.map(_:)` that used typed `throws` in [SE-0413][], it continued to support clients expecting the old `rethrows`-based `map` by using a `@_silgen_name` hack and `@usableFromInline internal`:

```swift extension Collection {     // New `map(_:)` using typed `throws`:     @inlinable     @backDeployed(...)          // slight lie, but that's irrelevant here     public func map<T, E>(         _ transform: (Element) throws(E) -> T     ) throws(E) -> [T] {         // ...actual implementation of `map` omitted...     }

// Wrapper with the same ABI as the old `map(_:)` which used `rethrows`:     @_silgen_name("$sSlsE3mapySayqd__Gqd__7ElementQzKXEKlF")     //             ^-- func map<$T>(_: (Self.Element) throws -> $T) rethrows -> Swift.Array<$T>     //                 in Swift.Collection extension from module Swift     @usableFromInline     func __rethrows_map<T>(         _ transform: (Element) throws -> T     ) throws -> [T] {           // 'throws' and 'rethrows' have the same ABI         try map(transform)      // calls through to the new `map(_:)`     } } ```

This creates a declaration which is written in Swift source code as `__rethrows_map(_:)`, but which has the mangled name of a function named `map(_:)`. When a module is compiled against this new version of the standard library, calls to `map(_:)` will use the new method directly; if a module is compiled against an older standard library, though, it will end up calling the `__rethrows_map(_:)` compatibility wrapper instead.

Although it is a powerful tool, `@_silgen_name` has its own set of serious drawbacks:

* It has absolutely no compile-time safety checking. * It works only with functions and is incompatible with certain function   features like opaque return types and `@backDeployed`.<sup>[1]</sup> * It requires deep knowledge of the name mangling and calling convention to use   correctly.    In practice, you basically need to be a Swift compiler or runtime engineer to use it correctly. For this reason `@_silgen_name` has never been proposed to Swift Evolution or recommended for general use.

Library maintainers need a tool that is much more flexible than `@preconcurrency` but also much safer and more ergonomic than `@_silgen_name`.

> *[1] This is because the name mangling has facilities to create multiple > symbols that are all related to the same declaration, but `@_silgen_name` > only provides an override for the name of the main symbol. Any declaration > that requires more than one symbol—such as a type declaration, a function > with an opaque return type, or a function with a back-deployment > thunk—would have no way to generate a mangled name for these additional > symbols.*

[SE-0413]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0413-typed-throws.md#effect-on-abi-stability

## Proposed solution

We propose a new attribute, called `@abi`, which specifies an alternate declaration that provides its ABI for name mangling purposes. This alternate declaration is enclosed within the argument parentheses; it has no body or initializer expression but is otherwise a syntactically complete declaration.

For example, the `@_silgen_name`-using `__rethrows_map(_:)` method shown in the Motivation section could be written much more clearly by using `@abi`:

```swift extension Collection {     // Wrapper with the same ABI as the old `map(_:)` which used `rethrows`:     @abi(         func map<T>(             _ transform: (Element) throws -> T         ) rethrows -> [T]     )     @usableFromInline     func __rethrows_map<T>(         _ transform: (Element) throws -> T     ) throws -> [T] {           // 'throws' and 'rethrows' have the same ABI         try map(transform)      // calls through to the new `map(_:)`     } } ```

Notice how the `@abi` attribute basically contains the original version of the declaration. When Swift is performing name mangling, this declaration is what it will use; for all other functions, it will use the outer `__rethrows_map` declaration. In particular, the `map(_:)` call in the body doesn't get resolved to the `map(_:)` function in the `@abi` attribute; it looks for other implementations and eventually finds the new typed-throws `map(_:)`.

What's more, the ABI declaration can be checked against the original one to make sure they're compatible. For example, at the ABI level `throws` and `rethrows` are interchangeable, but a non-`throws`/`rethrows` method handles its return values differently from them. If the maintainer accidentally dropped the `throws` effect while implementing this function, the compiler would complain about the mismatch:

```swift extension Collection {     @abi(         func map<T>(             _ transform: (Element) throws -> T         ) rethrows -> [T]       // error: 'rethrows' doesn't match API     )     @usableFromInline     func __rethrows_map<T>(         _ transform: (Element) throws -> T     ) -> [T] {                  // Whoops, should be 'throws' or 'rethrows'!         try map(transform)     } } ```

This checking also makes sure that the details specified in the `@abi` attribute are actually relevant. For example, the `@abi` attribute automatically inherits the access control, availability, and `@objc`-ness of the API it's attached to, so these are omitted from the `@abi` attribute. Default arguments, too, are left out because they're irrelevant to ABI. The compiler will diagnose this unnecessary information and suggest removing it.

All sorts of precision changes are possible. Here's another use for a `@_silgen_name` hack in the standard library: The maintainers discovered a data race safety bug in an API that had already shipped and needed to add an `@Sendable` attribute to prevent it, but `@preconcurrency` alone would have also suppressed the `@Sendable` attribute on the parameter that had been correctly annotated. `@abi` makes it easy to fix this sort of problem:

```swift public struct AsyncStream<Element> {     // ...other declarations omitted...          @abi(         init(             unfolding produce: @escaping /* not @Sendable */ () async -> Element?,             onCancel: (@Sendable () -> Void)? = nil         )     )     @preconcurrency     public init(         unfolding produce: @escaping @Sendable () async -> Element?,         onCancel: (@Sendable () -> Void)? = nil     ) {         // Implementation omitted     } } ```

Because `@preconcurrency` is applied to the outer declaration, but not to the one inside the `@abi` attribute, its typechecking effects will be applied (improving source compatibility for code written before the second `@Sendable` was added) but its name mangling effects will not (keeping the mangled name stable to preserve ABI compatibility).

This feature goes beyond what `@_silgen_name` could do, however. For example, it can be applied to `var` and `let` declarations:

```swift @abi(var oldName: Int) public var newName: Int ```

The mangled name of an accessor includes the mangled name of the variable or subscript it belongs to; thanks to `@abi`, the accessors for this variable will have `oldName` mangled into their names.

### Supported changes (and unsupported uses of them)

This feature can be used to override the mangling of a declaration's:

* Name, argument labels, and (for unary operator functions) fixity (`prefix`   vs. `postfix`)

* Preconcurrency status, actor isolation (where this does not affect calling   convention), and execution environment    * Generic constraints to marker protocols (`BitwiseCopyable`, `Copyable`,   `Escapable`, `Sendable`)    * Certain aspects of parameter and `self` behavior (variadic (vs. `Array`);   `@autoclosure`; `sending`; ownership specifiers as long as the behavior is   compatible)

* Certain aspects of argument, result, and thrown types (marker protocols in   existentials; tuple element labels; `@escaping`, `@Sendable`, and `sending`   results on closures)

Note that some of these changes relate to safety properties of your code, such as data race safety and escapability. When you use `@abi` to maintain ABI compatibility with older versions of your library while tightening safety constraints for new clients, you must take special care to remember that clients compiled without those changes may violate the new constraints. In practice, this means that you should probably only use `@abi` to make retroactive changes to safety constraints when you know that violating the constraint was *always* unsafe and it simply wasn't enforced until now.

For instance, the `AsyncStream.init(unfolding:onCancel:)` example above adds `@Sendable` to a closure parameter that previously didn't have the attribute. This is appropriate because the closure was *always* run concurrently; code that passed a non-`@Sendable` closure was already buggy, so this change merely made the bug easier to detect. It would have been inappropriate if the closure was originally run synchronously and was changed to run concurrently, because code that previously worked fine would now have new data races.

(`@abi` can still be used to help implement behavior changes, but the pattern is different: you make the original version `@usableFromInline internal` and change its API name to something you won't use by accident, applying `@abi` to keep its mangled name the same as always. Then you create a new declaration with the old API name and the behavior changes, using `@backDeployed` to ensure that new binaries can interoperate with old versions of your library. `__rethrows_map(_:)` is a good example of this pattern.)

In short: Much like when `@inlinable` is used, **it is the developer's responsibility to ensure that the current behavior of the declaration is compatible with clients built against older versions of it. The compiler doesn't understand the history of your codebase and cannot detect some mistakes.**

## Detailed design

### Grammar

An `@abi` attribute's argument list must have exactly one argument, which in this proposal must be one of the following productions:

* *function-declaration* * *initializer-declaration* * *constant-declaration* * *variable-declaration* * *subscript-declaration*

This argument must *not* include any of the following sub-productions:

* *code-block* * *getter-setter-block* * *getter-setter-keyword-block* * *willSet-didSet-block* * *initializer* (initial value expression)

To that end, we amend the following productions in the Swift grammar to make code blocks optional:

```diff  initializer-declaration → initializer-head generic-parameter-clause?                            parameter-clause async? throws-clause? -                          generic-where-clause? initializer-body +                          generic-where-clause? initializer-body?

initializer-declaration → initializer-head generic-parameter-clause?                            parameter-clause async? 'rethrows' -                          generic-where-clause? initializer-body +                          generic-where-clause? initializer-body?

subscript-declaration → subscript-head subscript-result generic-where-clause? -                        code-block +                        code-block? ```

We don't need to worry about ambiguity in terminating these productions because the block-less forms always occur in `@abi` attributes; its closing parenthesis serves as a terminator for the declaration.

> **Note**: If future development of the `@abi` attribute requires additional > information to be added to it, this can be done by adding new productions at > the beginning of the argument list, terminated by a comma or colon to > distinguish them from declaration modifiers: > > ```swift > @abi(unchecked, func liveDangerously(_: AnyObject))       // Future direction > func liveDangerously(_ object: AnyObject?) { ... } > ```

### Terminology and basic concepts

Syntactically, an `@abi` attribute involves two declarations. The *ABI-only declaration* is the one in the attribute's argument list; the *API-only declaration* is the one the attribute is attached to.

```swift @abi(func abiOnlyDeclaration()) func apiOnlyDeclaration() {} ```

A declaration which does not involve an `@abi` attribute at all—that is, which is neither API-only nor ABI-only—is called a *normal declaration*.

There are two *ABI roles*:

* An *API-providing declaration* determines the behavior of the declaration in   source code: what name developers write to address it, what constraints and   behaviors are applied at use sites, how it is implemented (its body,   accessors, or members), etc.

* An *ABI-providing declaration* determines how the declaration affects mangled   symbol names--both its own name and any names derived from it.

Every declaration has at least one of these roles. Every declaration also has a *counterpart* which fulfills the roles it does not. When the compiler wants to compute some aspect of a declaration pertaining to a role that declaration does not have, it automatically substitutes the declaration's counterpart.

Roles and counterparts work as follows:

| Declaration is… | ABI-providing | API-providing | Counterpart                                 | | --------------- | ------------- | ------------- | ------------------------------------------- | | Normal          | ✅            | ✅            | Is its own counterpart                      | | ABI-only        | ✅            |               | Declaration `@abi` attribute is attached to | | API-only        |               | ✅            | Declaration in `@abi` attribute             |

### Declaration checking

When you use the `@abi` attribute, Swift validates various aspects of the ABI-providing declaration in light of its API counterpart. An *aspect* is any way in which the external appearance of a declaration might vary. Attributes and modifiers are aspects, but so are the declaration's name, its result or value types, its generic signature (if it has one), its parameter list (if it has one), its effects (if it has any), and so on.

#### Aspects with no ABI impact must be omitted

Many aspects of a declaration only matter for an API-providing declaration; they're irrelevant on a declaration that's ABI-only. These include:

* Default arguments for parameters

* Attributes which only affect compile-time checking or behavior, such as   `@unsafe`, `@discardableResult`, or result builder attributes

* Certain attributes and modifiers which have ABI effects, but where the   compiler has been designed to inherit the ABI-providing declaration's   behavior from its API counterpart where needed:

* `@objc` (and its ilk) and `dynamic`, including inference behaviors   * Access control modifiers and `@usableFromInline`   * `@inlinable` and other attributes controlling inlining   * `@available` and `@backDeployed`   * `override`

These aspects are generally *forbidden* on an ABI-providing declaration. If they are present, the compiler will diagnose an error and suggest they be removed.

In practice, this means that an `@abi` attribute is often significantly shorter than the declaration it's attached to because it doesn't need to specify as much information:

```swift @abi(     // Same signature as below, except `T` is not `Sendable`.     static     func assumeIsolated<T>(         _ operation: @MainActor () throws -> T,         file: StaticString,         line: UInt     ) rethrows -> T ) @available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)    // not needed in @abi @usableFromInline                                               // not needed in @abi internal                                                        // not needed in @abi static func assumeIsolated<T: Sendable>(     _ operation: @MainActor () throws -> T,     file: StaticString = #fileID,              // default argument not needed in @abi     line: UInt = #line                         // default argument not needed in @abi ) rethrows -> T {     ... } ```

The intended workflow is that a developer can paste the entire original declaration into an `@abi` attribute and the compiler will then tell them which parts of it they should remove.

#### Call compatibility

For aspects which *do* have ABI impact, the compiler enforces that the ABI-providing declaration is *call-compatible* with its API-providing counterpart. Broadly, "call compatibility" means that, other than the mangled names, the machine code generated to call the ABI-providing declaration would be equally able to call its API counterpart. For instance:

* Declarations are of the same fundamental kind (a `func` for a `func`, a `var`   for a `var`, etc.), so they expose the same basic capabilities and entry   points.

* Effects match closely enough that the caller and callee will agree on which   stack should be used and which implicit parameters will be passed.

* Inputs and outputs are passed similarly enough to ensure type and memory   safety, including compatible memory management behavior, and including the   implicit inputs and outputs used for generic parameters, `self`, and   throwing.

However, call compatibility does *not* require aspects of the declaration that only change the mangled name and/or compile-time checking to match:

* Names, argument labels, or other name-like traits of a declaration (such   as the fixity modifiers for operator functions) may vary.

* Aspects which affect only syntax (and possibly mangling) may vary. For   instance, a regular closure may be used instead of an `@autoclosure`; tuple   types with different element labels may be used; an array parameter may be   used instead of a variadic parameter; ordinary optionals may be used instead   of implicitly-unwrapped optionals; `throws` and `rethrows` may be used   interchangeably.    * Concurrency safety and lifetime restrictions which don't affect the ability   to call the declaration may vary. For instance, a non-escaping closure may be   used instead of an `@escaping` closure; `sending` modifiers, `Sendable`   constraints, or neither may be used interchangeably so long as memory   management isn't affected; isolation may vary so long as extra data does not   need to be passed; `~Copyable` and `~Escapable` constraints may vary.

#### Impact on redeclaration checking

A declaration must have a unique signature in each of its roles. That is, an API-only declaration is checked against API-providing declarations; an ABI-only declaration is checked against ABI-providing declarations; a normal declaration is checked twice, first against API-providing declarations and then against ABI-providing declarations.

In general, name lookup will return declarations with the API-providing role and will ignore declarations with the ABI-providing role. Even when you're writing an ABI-only declaration, you should use the API names of other declarations, not the ABI names.

#### Declaring multiple variables

When `var` or `let` is used, the ABI-providing declaration must bind the same number of patterns, each of which has the same number of variables, as its API counterpart. That is, the first of these is valid, while the others are not:

```swift // OK: @abi(var x, y: Int) var a, b: Int

// Mismatched: @abi(var x, y, z: Int) var a, b: Int

// Also mismatched: @abi(var x, y: Int) var a: Int, (b1, b2): (Int, Int)

// Mismatched even though the total adds up: @abi(var x, y, z: Int) var a: Int, (b1, b2): (Int, Int) ```

An ABI-providing declaration does *not* infer missing types from its API counterpart. In practice, this means that an ABI-providing declaration may need to explicitly declare types that its API counterpart infers from an initial value expression.

An ABI-providing `var` or `let` does not have a list of accessors or specify anything about them; in a sense, it can be thought of as inferring its accessors from its API counterpart.

### Limitations on feature scope

#### Supported declaration kinds

In this proposal, `@abi` may be applied only to `func`, `init`, `var`, `let`, and `subscript` declarations. Other declarations are less straightforward to support in various ways; see the future directions section for details.

#### Language features with auxiliary declarations

`@abi` can neither contain, nor be applied alongside, `lazy` or a property wrapper. These features implicitly create auxiliary declarations, and it isn't clear how those should interact with `@abi`.

#### Limited support for macros

Neither attached nor freestanding macros can be used inside an `@abi` attribute. None of the attached macro roles would be useful since ABI-providing declarations do not have bodies, members, accessors, or extensions; the freestanding macro roles, on the other hand, expand to complete declarations, while some of the future directions involve supporting special stub syntax which would be incompatible here.

`@abi` can still be applied *alongside* an attached macro or *to* a freestanding macro, although in practice many macros will need to handle `@abi` attributes specially.

### Non-normative: Precise rules as currently implemented

To help evaluate how these principles will work in practice, we've listed the current implementation's rules below. **However, we do not guarantee that the rules listed here will exactly match the final behavior of the feature.** Basically, we don't want to put every bug fix through an amendment or every tiny, straightforward expansion of capabilities through a proposal.

#### Must be omitted (no ABI impact or inheritance in place)

* Default arguments on parameters * Result builder attributes on parameters or declarations * `@available` * `@inlinable`, `@inline`, `@backDeployed`, `@usableFromInline`,   `@_alwaysEmitIntoClient`, `@_transparent` * Objective-C opt-in attributes (`@objc`, `@IBAction`, `@IBDesignable`,   `@IBInspectable`, `@IBOutlet`, `@IBSegueAction`, `@GKInspectable`,   `@NSManaged`, `@nonobjc`) * `optional` modifier in `@objc` protocols * `@NSCopying` * `@_expose` and `@_cdecl` * `@LLDBDebuggerFunction` * `dynamic` modifier and `@_dynamicReplacement` * `@specialize` on functions and initializers * `override` modifier * Access control (`open`, `public`, `package`, `internal`, `fileprivate`,   `private`) * Setter access control (`open(set)`, `public(set)`, `package(set)`,   `internal(set)`, `fileprivate(set)`, `private(set)`) * `@_spi` and `@_spi_available` * Reference ownership (`weak`, `unowned`, `unowned(unsafe)`) * `@warn_unqualified_access` * `@discardableResult` * `@implementation` on functions * `@differentiable`, `@derivative`, `@transpose` * `@noDerivative` on declarations other than parameters * `@exclusivity` * `@safe` and `@unsafe` * `@abi` * Unsupported features (`lazy`, property wrapper attributes, attached macro   attributes)

#### Must be specified and must match

* Declaration kind (`func`, `var`, etc.) * `convenience` and `required` modifiers on initializers * `distributed` modifier * Result type of functions * Failability of initializers * Value type of subscripts and variables * Number of parameters on functions, initializers, and subscripts * Parameter types * `inout` on parameters and `mutating`/`nonmutating` modifiers on members * `@noDerivative` on parameters * `@_addressable` on parameters and `@_addressableSelf` on members * `@lifetime` attributes (NOTE: this probably ought to be "vary with   constraints", but an interaction with `@_addressableForDependencies` needs   to be worked out) * `async` effect * Aspects of types which are not listed elsewhere

#### Allowed to vary, but with constraints

* `throws` effect and thrown type (`rethrows` is equivalent to `throws`) * Generic signature of functions, initializers, and subscripts (marker   protocols may vary) * Variadic parameter types (`T...` and `Array<T>` are treated as equivalent) * Parameter ownership specifiers and `self` ownership modifiers (ones with   equivalent memory management behavior may be substituted for one another) * `sending` on parameter and result types (so long as its ownership behavior   is preserved) * `static`, `class`, and `final` modifiers (`class final` is equivalent to   `static`) * Actor isolation (other than `@isolated(any)`, which is incompatible with   the others)

#### Allowed to vary arbitrarily

* Base names of functions and variables * Argument labels and parameter names * `prefix` and `postfix` modifiers on operator functions * Whether optionals are implicitly unwrapped * Element labels in tuple types * `@autoclosure` on parameters * `@escaping` on closures * `@Sendable` on closures * `isolated` on parameters * `_const` on parameters and variables * Generic parameter names * Use of type sugar (e.g. `Optional<T>` and `T?` are equivalent) * Use of generic types that have a same-type constraint (e.g. in the   presence of `T == Int`, `T` and `Int` are equivalent) * Use of marker protocols in existential types * `@Sendable` on functions and initializers * `@preconcurrency` * `@execution` * Aspects of declarations which are not listed elsewhere

## Source compatibility

This feature is additive and affects only the ABI. However, many of the changes that can be effected using it can be source-breaking unless done with care. For example:

* When renaming a declaration, make sure there's a declaration with the   original name that can be called in the same situations, and consider using   `@backDeployed` to ensure recompiled clients don't have to raise their   minimum deployment target.   * When a type changes, make sure that it will either become more broad, or that   you are willing to accept any breakage that results. For example, switching   from a `Sendable` constraint to a `(borrowing) sending` parameter strictly   increases the set of valid callers, so that's probably always okay; switching   from no constraint to a `Sendable` constraint, on the other hand, will break   some callers, but might be acceptable if the missing `Sendable` constraint   created an opportunity for data races.

## ABI compatibility

This feature is intended to give libraries additional options to evolve APIs without breaking the corresponding ABIs.

We are currently evaluating adoption in `stdlib/public`. So far, it looks like we can replace all uses of `@_silgen_name` to specify mangled Swift names with uses of `@abi`, and in some cases remove hacks; this will be roughly 75 declarations.

Note that this feature does not subsume the use of `@_silgen_name` with an arbitrary, C-style symbol name to either declare a function implemented in C++ using the Swift calling convention, or to generate a symbol that's easy to access from C-family code or a compiler intrinsic. About 200 of the uses of `@_silgen_name` in `stdlib/public` are of this type; we expect these to remain as-is.

## Implications on adoption

This feature is intended to help ease the adoption of other new features by allowing a declaration's ABI to be "pinned" to its original form even as it continues to evolve. Note that there is only ever a need to specify the *original* form of the declaration, not any revisions that may have occurred between then and the current form; there is therefore never a reason you would need to specify more than one `@abi` attribute, nor to tie an `@abi` attribute to a specific platform version.

In module interfaces, the `@abi` attribute is partially suppressible. Specifically, for `func`s that do not use `@backDeployed` and do not have opaque result types, the compiler emits a module interface that falls back to using an equivalent `@_silgen_name` attribute. For other declarations, however, the compiler falls back to an `@available(*, unavailable)` attribute instead, with a message indicating that the developer will need a newer compiler to use the declaration.

## Future directions

### Unchecked mode

There may be situations where a skilled engineer knows that a specific use of `@abi` is compatible, but the compiler does not know how to prove that. While that can often be considered a compiler bug—the checker *should* be able to tell that the code is safe—it may be useful, either as a workaround or to handle extreme edge cases, to be able to turn off `@abi`'s compatibility checking:

```swift @abi(unchecked, func liveDangerously(_: AnyObject)) func liveDangerously(_ object: AnyObject?) { ... } ```

### Support for types and extensions

It ought to be possible to use `@abi` with types:

```swift @abi(struct Buffer: ~Copyable) public struct FrameBuffer: ~Copyable { ... }

@available(*, unavailable, renamed: "FrameBuffer") @abi(typealias FrameBuffer)  // keeps `typealias Buffer` from colliding with `struct Buffer` typealias Buffer = FrameBuffer ```

Here, the library maintainer discovered after shipping that the name `Buffer` is too vague—clients didn't understand what it meant, and some of them even had another type named `Buffer`. When they rebuild with the new version of the library, they will get an error with a fix-it to change `Buffer` to `FrameBuffer`, but it will still use the name `Buffer` at the ABI level so that existing binaries don't break. This will apply not only to the type itself, but also to its members and even to functions with a `FrameBuffer` in their overload signature.

Type renaming may create challenges for module interface source stability, since a module interfaces could refer to a type by an older or newer name than its current one. It might be possible to address this by making module interfaces always refer to types by their ABI name. (This should be non-breaking as long as it's introduced at the same time as `@abi` for types.)

This could also be used to affect the inference of properties of other declarations. Consider this example:

```swift @abi(protocol Component) @preconcurrency @MainActor      // Added after shipping public protocol Component { ... }

extension Component {     public func onEvent(_ handler: @Sendable @escaping () -> Void) -> some Component { ... } } ```

The library maintainer decided after the fact that `Component`s should be isolated to the main actor, but that broke some Swift 5 code, so they added `@preconcurrency` for its typechecking effects. However, `@preconcurrency` then got applied to `onEvent(_:)`, suppressing its `@Sendable` attribute, which changed its ABI. Using `@abi` on `Component` should override the ABI effects of `@preconcurrency` not just for `Component` itself, but for every declaration nested inside it.

To support this kind of inference, the compiler may need to add an inferred `@abi` attribute when a declaration with both roles depends on one which provides only one role. For instance, if a type conforms to a protocol whose `@abi` attribute specifies different actor isolation or different marker protocols, Swift may need to add an inferred `@abi` attribute so the type's ABI will be compatible with the protocol's ABI while its API will be compatible with the protocol's ABI.

### Support for enum cases

It would probably be possible to allow `@abi` to be attached to a `case` declaration, allowing it to backwards-compatibly rename or otherwise control the ABI of enum cases.

### Support for auxiliary declarations

It might be possible to allow `@abi` to be used with `lazy` and property wrappers either by coming up with rules to derive an `@abi` attribute for those declarations, or by creating a syntax that can specify them:

```swift @abi(nonisolated var currentValue: Int) @abi(for: projection, nonisolated var $currentValue: Binding<Int>) @abi(for: storage, nonisolated var _currentValue: Binding<Int>) @MainActor @Binding var currentValue: Int ```

### Support for accessors

It might be possible to allow `@abi` to be attached to individual accessors.

### Support for context changes

It might be possible to allow an ABI-providing declaration to belong to a different context than its counterpart—for instance, turning a global variable into a static property, or moving a method to a single-property `@frozen` wrapper struct.

A particularly interesting one might be allowing an extension member to be mangled as a member of the main type declaration, or vice versa, since users may not be aware of the ABI impact of moving a declaration from one to the other.

### Equivalent type attribute

Many uses of `@abi` only change one or two types in a complicated declaration. It might be possible to provide an `@abi` *type* attribute that can be applied on the spot as a shorthand:

```swift public func runConcurrently(     _ body: @escaping @abi(() -> Void) @Sendable () -> Void ) { ... }

// Equivalent to: @abi(func runConcurrently(_: @escaping () -> Void)) public func runConcurrently(     _ body: @escaping @Sendable () -> Void ) { ... } ```

### Support for moving declarations to a different module

It might be possible to roll some of the functionality of the compiler-internal `@_originallyDefinedIn` attribute into this attribute.

## Alternatives considered

### Many narrow features

The original motivation for this proposal involved fairly narrow cases where `@preconcurrency` was too blunt an instrument, such as suppressing the ABI impact of one sendability annotation while leaving others intact. One could imagine designing individual type or decl attributes for each specific change one might wish to make, but this would require both a lot of effort from compiler engineers to design a specific tool for each problem, and a lot of effort from library maintainers to figure out which tool to apply to a given task and how to use it.

### An argument that describes the differences

Rather than creating many totally separate features, one could imagine an `@abi` declaration attribute with an argument list which somehow described the differences between the API and ABI. However, we see use cases for changing virtually every aspect of an API—its name; adding or removing declaration attributes, modifiers, and effects; adding or removing inherited protocols and generic constraints; changing parameter, result, and error types; changing type attributes and modifiers; even changing individual sub-types within generic types, function types, and protocol compositions at any position in the declaration—and a mini-language to address and edit all of these different aspects of a declaration seems difficult to design and tedious to learn. By contrast, re-specifying the entire declaration in the argument reuses the developer's existing knowledge of how to read and write declarations and gives them an easy way to adopt it (just copy the existing declaration into an `@abi` attribute before you start editing in new features).

### Syntax where the two declarations are peers

We could design this differently such that the API-only and ABI-only declarations are peers in the same context:

```swift // This declaration provides the ABI... @abi(for: __rethrows_map(_:)) @usableFromInline func map<T>(     _ transform: (Element) throws -> T ) rethrows -> [T]

// ...for this declaration @usableFromInline func __rethrows_map<T>(     _ transform: (Element) throws -> T ) throws -> [T] {     try map(transform) } ```

In theory, this design could simplify parsing, since the `@abi` attribute's argument might just be an ordinary expression. However, it introduces several complications:

1. Merely giving the name of a declaration may not be specific enough in the    presence of overloads, or even when the API and ABI have the same name but    slight type differences. We might normally tell developers to work around    this by using more specific names, but that's not really an appropriate    answer for a tool which is designed to allow fine control of API and ABI    naming. 2. A lot of compiler logic would have to be modified to filter out ABI-only or    API-only declarations when it walked through lists of top-level decls or    members. The current design, where the ABI-only declarations are tucked away    in attributes, keeps them from being accessed by accident. 3. If the future direction for `@abi` on type declarations is taken, the    productions for full type declarations will not be suitable, as they require    member blocks.

## Acknowledgments

Thanks to Holly Borla for recognizing the need for an `@abi` attribute.
