# \`@convention(thin)\` function pointers

**URL:** <https://forums.swift.org/t/convention-thin-function-pointers/65180>\
**Category:** Pitches\
**Created:** [May 24, 2023, 11:13pm UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180 "2023-05-24T23:13:26Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [May 24, 2023, 11:13pm UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/1 "2023-05-24T23:13:26Z")

</div>

Swift closures normally consist of two pointer-sized fields, one for the function entry point, and one for a reference to the captures. There are many occasions where developers want to work abstractly with functions that are known not to have any captures, and they would like to statically enforce that no capture context is allocated, and get the size and performance benefits of passing around a single, non-reference-counted function pointer. Officially, the only way to do this in Swift today is with `@convention(c)` function types, but these types are limited by C interop, and can only have parameters and returns of C-compatible types. Unofficially, there's `@convention(thin)`, but it was never intended for human consumption, and attempting to use it will cause compiler crashes and miscompiles for anyone not intimately aware of Swift's implementation details. Let's talk about fixing that so that `@convention(thin)` can become an officially supported feature.

## Making calling convention details explicit

Swift function types by default abstract away a number of calling convention details, and Swift will automatically convert between different calling conventions for the same high-level function type. However, it does this by capturing the old function value in order to create the new function value, and we can't generally do that with a function representation that doesn't support captures. The relevant details include:

- what ownership convention is used to manage the parameter's lifetime
- whether a parameter is passed or returned indirectly (as a pointer to a memory location containing the value) or directly (as one or more registers directly containing the value)

[SE-0377](https://github.com/apple/swift-evolution/blob/main/proposals/0377-parameter-ownership-modifiers.md) already puts the ownership convention under developer control with the `borrowing` and `consuming` modifiers, and [SE-0390](https://github.com/apple/swift-evolution/blob/main/proposals/0390-noncopyable-structs-and-enums.md) made these modifiers a requirement for parameters of `~Copyable` type, since the convention cannot generally be abstracted away without the ability to implicitly copy values. For similar reasons, I think we could say that a `@convention(thin)` function type must make its parameter and return ownership conventions explicit, as well as the indirectness of those parameters and returns. In addition to the existing `consuming`/`borrowing`/`inout` modifiers for specifying parameter convention, we could also introduce `direct` and `indirect` modifiers to explicitly state the indirectness of parameters and returns, and so you can state a thin function type with full specificity as:

```swift
func foo(_ x: Int) -> Void {
  print(x)
}

func bar(_ x: String) -> Void {
  print(x)
}

func callTwice<T>(_ fn: @convention(thin) (indirect borrowing T) -> direct Void, with value: T) {
  fn(value)
  fn(value)
}

callTwice(foo, with: 17)
callTwice(bar, with: "38")

```

In order to maintain source compatibility with the existing feature, we can start warning if a `@convention(thin)` function doesn't explicitly state the directness and ownership convention of any parameters or returns.

That said, having to specify explicit directness and ownership for every parameter is very verbose. We could conceivably still default to `borrowing` ownership for copyable parameters. We could also decide to make one of `direct` or `indirect` implicit, and have only the other need to be stated explicitly. Only certain types support being passed or returned directly, and `indirect` arguments are more amenable to type punning and other shenanigans for the iron-hearted, so `indirect` might be a better default if we decide to go that way.

## Manually setting the symbol name for Swift functions

Another unofficial feature we've had for a long time, and told people not to use if there are any alternatives for as long, is the `@_silgen_name` attribute, which sets the symbol name to use for the Swift entry point of a function. We ward people away from this feature for similar reasons to `@convention(thin)`, that it isn't possible to use correctly without deep knowledge of the Swift implementation. However, with full manual control of the calling convention, and proper support for thin function pointers, a lot of that reason goes away, since you could then conceivably write in one binary:

```swift
@symbolName("foo")
public func foo(x: direct borrowing Int, y: indirect borrowing Any) { ... }

```

and in another binary do:

```swift
let foo = unsafeBitCast(
  dlsym(sohandle, "foo")!,
  to: (@convention(thin) (direct borrowing Int, indirect borrowing Any) -> ()).self)
foo(17, "hello")

```

to dynamically look up the symbol, which you can't do today.

---

<div class="post-metadata">

**Author:** ![tera](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tera/32/38201_2.png) [@tera](https://forums.swift.org/u/tera)\
**Post date:** [May 24, 2023, 11:50pm UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/2 "2023-05-24T23:50:30Z")

</div>

> [@](#):
>
> Swift closures normally consist of two pointer-sized fields, one for the function entry point, and one for a reference to the captures.

Some time ago I came up with [an idea](https://forums.swift.org/t/add-api-that-makes-it-easier-to-interoperate-with-c-callbacks/62718/13) of using a single pointer sized values for swift closures. Could that fly?

---

<div class="post-metadata">

**Author:** ![Alejandro](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/alejandro/32/46197_2.png) [@Alejandro](https://forums.swift.org/u/Alejandro)\
**Post date:** [May 25, 2023, 12:19am UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/3 "2023-05-25T00:19:54Z")

</div>

> [@tera](#):
>
> Some time ago I came up with [an idea](https://forums.swift.org/t/add-api-that-makes-it-easier-to-interoperate-with-c-callbacks/62718/13) of using a single pointer sized values for swift closures. Could that fly?

We cannot change the representation of closures at this point.

---

<div class="post-metadata">

**Author:** ![jrose](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jrose/32/408_2.png) [@jrose](https://forums.swift.org/u/jrose)\
**Post date:** [May 25, 2023, 1:20am UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/4 "2023-05-25T01:20:16Z")

</div>

So this is sneakily a proposal for `direct`/`indirect`. :-) I do have some questions/concerns:

- Does directness + ownership fully capture the calling convention for _all_ types, and are we sure that will be true in the future?

- Will the compiler have to diagnose types that can't be passed directly? Up until now I think that hasn't been a Sema-level (roughly, "type-checking time") concept, and maybe it's still something that can be deferred to SIL diagnostics (roughly, "flow-sensitive diagnostics that run later in compilation than type-checking"). But I do worry about people trying to use `direct NonFrozenStruct` outside the original module and getting weird errors from the compiler, and I myself don't know whether `direct Any` or `indirect NSObject` are valid concepts or not.

- Having `@symbolName` without `@cdecl` worries me: I think people will start exposing Swift functions to C [even more than they already do], and have things mostly work but then cause extremely weird errors in the corner cases. (But while [`@cdecl` isn't _that_ much more work](https://forums.swift.org/t/formalizing-cdecl/40677), it's a lot more than this…)

- The dlsym code is only correct if both sides use the same ABI, which could be a cause of weird issues on non-stable-ABI platforms if people don't think about it. But there _is_ an unsafeBitCast there, so maybe it's ""fine"".

---

<div class="post-metadata">

**Author:** ![griotspeak](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/griotspeak/32/1966_2.png) [@griotspeak](https://forums.swift.org/u/griotspeak)\
**Post date:** [May 25, 2023, 7:02am UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/5 "2023-05-25T07:02:11Z")

</div>

I will have to read again this when I am less tired but I'm glad to see this feature.

This is strictly about function pointers, correct? Are there any implications for methods accessed/passed as closures/function pointers?

---

<div class="post-metadata">

**Author:** ![Geordie\_J](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/geordie_j/32/839_2.png) [@Geordie\_J](https://forums.swift.org/u/Geordie_J)\
**Post date:** [May 25, 2023, 8:57am UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/6 "2023-05-25T08:57:11Z")

</div>

I will start by saying I don’t have full enough context to understand which problem these particular direct/indirect keywords are solving, but with all these additional keywords popping up in the last couple of years, Swift is starting to feel like C++.

My gut feeling on this proposal is therefore a -1. I feel like Swift should be Swift. If we want to use C++ features, we should just use C++ (there is active work on that, which I welcome and support). I don’t really buy the argument of “just don’t use it” in the case of additional keywords.

---

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [May 25, 2023, 3:39pm UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/8 "2023-05-25T15:39:44Z")

</div>

> [@jrose](#):
>
> - Does directness + ownership fully capture the calling convention for _all_ types, and are we sure that will be true in the future?

So there are a couple of calling convention variations I can think of that aren't entirely addressed by this:

- Tuples may or may not be exploded into individual parameters or returns depending on the abstraction level. We could however say that, if you mark a tuple with explicit directness, then it's always treated as a single aggregate, and to match a convention that destructures the tuple, you can specify it as individual arguments.
- Methods and closure invocation functions use the `@convention(method)` calling convention in SIL, which puts the `self` parameter in the special context register and preserves it across calls. It could be useful to expose `@convention(method)` as a surface-language-level convention too, perhaps, in order to allow for things like [dynamically constructing protocol conformances](https://forums.swift.org/t/dynamically-conforming-a-type-to-a-protocol-at-runtime/65131), splitting a closure into separate context reference and invocation function values, and so on.

> [@jrose](#):
>
> - Will the compiler have to diagnose types that can't be passed directly? Up until now I think that hasn't been a Sema-level (roughly, "type-checking time") concept, and maybe it's still something that can be deferred to SIL diagnostics (roughly, "flow-sensitive diagnostics that run later in compilation than type-checking"). But I do worry about people trying to use `direct NonFrozenStruct` outside the original module and getting weird errors from the compiler, and I myself don't know whether `direct Any` or `indirect NSObject` are valid concepts or not.

Yeah, we'd have to formalize the rules for what subset of types are allowed to be passed `direct`. That would at least include:

- a `struct` or `enum` that's either `@frozen` or in the same module, and whose elements are all either `indirect` or can themselves be passed directly
- an object reference of `class` type
- a generic type or existential with an `AnyObject` constraint (either directly or implied)

> [@jrose](#):
>
> Having `@symbolName` without `@cdecl` worries me: I think people will start exposing Swift functions to C [even more than they already do], and have things mostly work but then cause extremely weird errors in the corner cases. (But while [`@cdecl` isn't _that_ much more work](https://forums.swift.org/t/formalizing-cdecl/40677), it's a lot more than this…)

At the risk of tying the feature to a boat anchor, maybe `symbolName` could take on the responsibliity for manually specifying both C and Swift symbol names, since a lot of the additional modifiers such a feature might need, like visibility, section, usedness, and so on, could apply independently to the Swift and C entry points. So you could end up writing something like

```swift
@symbolName(swift: "swift_foo", visibility: internal)
@symbolName(c: "c_foo", visibility: public, section: " __TEXT,__ fooplugn", used)
func foo(...)

```

if you wanted to export a Swift entry point internally for some other Swift code to indirectly link against, while exporting the C entry point to be consumed by some plugin or other interface that needs it.

> [@jrose](#):
>
> The dlsym code is only correct if both sides use the same ABI, which could be a cause of weird issues on non-stable-ABI platforms if people don't think about it. But there _is_ an unsafeBitCast there, so maybe it's ""fine"".

Maybe we could have a way to still semi-mangle the name to include the expected function type of the entry point, so that if you try to look it up with a different type, the lookup still fails.

---

<div class="post-metadata">

**Author:** ![allevato](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/allevato/32/385_2.png) [@allevato](https://forums.swift.org/u/allevato)\
**Post date:** [May 25, 2023, 3:46pm UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/9 "2023-05-25T15:46:50Z")

</div>

> [@Joe\_Groff](#):
>
> At the risk of tying the feature to a boat anchor, maybe `symbolName` could take on the responsibliity for manually specifying both C and Swift symbol names, since a lot of the additional modifiers such a feature might need, like visibility, section, usedness, and so on, could apply independently to the Swift and C entry points. So you could end up writing something like
> 
> ```swift
> @symbolName(swift: "swift_foo", visibility: internal)
> @symbolName(c: "c_foo", visibility: public, section: " __TEXT,__ fooplugn", used)
> func foo(...)
> 
> ```

We also currently have `@_expose(Cxx, "external_symbol_name")` for C++ interop, so we should _really_ aim to unify all of these under a single umbrella.

---

<div class="post-metadata">

**Author:** ![Alex\_L](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/alex_l/32/6202_2.png) [@Alex\_L](https://forums.swift.org/u/Alex_L)\
**Post date:** [May 25, 2023, 5:23pm UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/10 "2023-05-25T17:23:04Z")

</div>

Note that `@_expose` doesn't specify the symbol name, it specifies the name of the C++ declaration that represents the original Swift declaration in C++, similar to the `NS_SWIFT_NAME` macro for Objective-C interoperability. We also don't require it anymore for exposing things to C++, so it might not make sense to unify it with `symbolName`.

---

<div class="post-metadata">

**Author:** ![allevato](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/allevato/32/385_2.png) [@allevato](https://forums.swift.org/u/allevato)\
**Post date:** [May 26, 2023, 1:58am UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/11 "2023-05-26T01:58:01Z")

</div>

Yeah, all of the existing attributes for exporting symbols to various FFI are different in very subtle ways, which I believe are the following:

1. `func f()` without attributes:

2. `@_silgen_name("g") func f()`

3. `@_cdecl("g") func f()`

4. `@_expose(Cxx, "g") func f()`

5. `@objc(g) func f()` (where `f` is a method in a `class`):

So I agree that there's a bit of a spectrum here; `@_silgen_name` affects the name of the _original_ symbol, `@_cdecl` and `@objc` leave the original symbol alone and introduce a _new_ one for external clients, and `@_expose(Cxx, ...)` only affects the generated header.

I don't think that's a reason to avoid unifying these concepts altogether (although the ship has already sailed for `@objc`, which is both officially supported and very widely used). If we claim that `@symbolName` (or whatever it's called) "defines the name that foreign languages interact with a symbol", then I think we could argue that `@_cdecl` and `@_expose(Cxx, ...)` fit that description even if C++ interop doesn't require it, _and_ I'd feel comfortable saying that `@symbolName(swift, ...)` is defining the name that foreign languages (or a foreign process via `dladdr`) which aren't using Swift's C++ interop use to interact with a function that still uses a Swift calling convention, since those may be the only times you'd want to rename the _Swift_ symbol. (I suppose maintaining ABI compatibility while renaming an API is another one.)

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [May 28, 2023, 9:58am UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/12 "2023-05-28T09:58:33Z")

</div>

> [@Joe\_Groff](#):
>
> ```swift
> func foo<T>(_ a: Int, _ b: T) -> T {
> return b
> }
> 
> func getFoo<T>(of type: T.Type) -> @convention(thin) (direct borrowing Int, indirect borrowing T) -> indirect T {
> return foo
> }
> 
> ```

Being generic, function `foo` also accepts a hidden parameter for metatype of `T`.

We could transform it’s signature to make it explicit:

```swift
let f: @convention(thin) (String.Type, direct borrowing Int, indirect borrowing String) -> indirect String = getFoo(of: String.self)

```

But that won’t work if generic signature involves protocol conformances:

```swift
func bar<T: Hashable>(_ x: T) {}
let b: @convention(thin) (String.Type, {{{Conformance of String to Hashable?}}}, indirect borrowing String) = bar

```

---

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [May 30, 2023, 2:33pm UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/13 "2023-05-30T14:33:24Z")

</div>

Thanks, that code is actually invalid since it is would need to capture `T` from `getFoo` in order to form the function value. I'll change the example to something else. I don't think we'd want to expose generic parameters as regular value parameters in that way, because the relationship between generic signatures and calling convention parameters is complex, and in general, we would lose type safety since the types of value parameters and returns generally depend on the type parameters. If we wanted to support taking pointers to generic functions, the best way to do that would be to support generic function/closure types directly.

---

<div class="post-metadata">

**Author:** ![John\_McCall](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/john_mccall/32/1351_2.png) [@John\_McCall](https://forums.swift.org/u/John_McCall)\
**Post date:** [May 30, 2023, 6:37pm UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/14 "2023-05-30T18:37:27Z")

</div>

I wonder if we shouldn't just impose the same generics restrictions for this that we do for `@convention(c)`. That would define away any questions around substitution, which ought to mean we also don't need to add `@direct` / `@indirect` and all of that complexity. The resulting feature would still have significant benefits over `@convention(c)`: it wouldn't do any implicit bridging, it would support the full set of (concrete) Swift types, and it would use the superior-in-principle Swift calling conventions. It wouldn't let you re-implement generic v-tables, but I think it's reasonable to draw the line there for a variety of reasons.

---

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [May 30, 2023, 7:34pm UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/15 "2023-05-30T19:34:52Z")

</div>

That's an interesting direction to consider. However, in the standard library, there are existing uses of `@convention(thin)` functions in generic contexts, which are aligned to match calling conventions used by compiler-generated code. If in the future we were to, say, write more of the Swift runtime in Swift rather than C++, I'd expect to only see more thin functions in generic contexts with specific calling convention needs.

Maybe another way we could subset out the substitution issues is to make it so that all parameters are maximally abstracted, meaning they're indirect, unexploded, most-abstract-representation, but you can still opt specific parameters or returns into being direct, and _those_ direct parameters and returns follow the same constraints as we currently apply to `@convention(c)`.

---

<div class="post-metadata">

**Author:** ![John\_McCall](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/john_mccall/32/1351_2.png) [@John\_McCall](https://forums.swift.org/u/John_McCall)\
**Post date:** [May 31, 2023, 12:18am UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/16 "2023-05-31T00:18:06Z")

</div>

I understand the desire to allow that kind of runtime code to be written in Swift, but my experience with v-table-like structures is that generating provably-secure code around both their use and their construction requires either (1) a _lot_ of manual care or (2) compiler involvement. So I'm not enthusiastic about the idea of doing it by providing a very general language feature that then defaults to being hard to lock down. (Of course, you can run into this problem today with ordinary function values, but the awareness of the costs around them can hopefully lead people towards the path of understanding the other disadvantages of doing this manually.) The `KeyPath` code in the stdlib that you mentioned is a good example of this: it's only _using_ a table of thin functions, not _emitting_ one, but even so it's really at pains to handle pointer authentication, and it risks degrading the security quite a bit by trying to synthesize compound operations out of intrinsics without a guarantee that they'll be emitted in a unit.

I think maybe a less first-class feature where the thin functions were tied to a containing structure would be a better way to achieve that kind of effect. That structure can simultaneously provide (1) a pattern for the abstraction of the functions and (2) a basis for storing the function pointers with more reasonable security by default. In practice it's really rare to want to traffic in thin functions directly outside of a v-table-like containing structure; first-class thin functions are very brittle as an abstraction mechanism.

---

<div class="post-metadata">

**Author:** ![esummers](https://avatars.discourse-cdn.com/v4/letter/e/f04885/32.png) [@esummers](https://forums.swift.org/u/esummers)\
**Post date:** [May 31, 2023, 1:25am UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/17 "2023-05-31T01:25:59Z")

</div>

> [@Geordie\_J](#):
>
> with all these additional keywords popping up in the last couple of years, Swift is starting to feel like C++.

That is deliberate since Swift wants to be the next C++. A goal of Swift is to write for low-level systems that need a lot of precise control. You could just ignore all this stuff if you are writing high-level code. From a high-level coder perspective these things should get good warnings and fix-its from the compiler.

---

<div class="post-metadata">

**Author:** ![JetForMe](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jetforme/32/482_2.png) [@JetForMe](https://forums.swift.org/u/JetForMe)\
**Post date:** [May 31, 2023, 2:34am UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/18 "2023-05-31T02:34:20Z")

</div>

> [@esummers](#):
>
> A goal of Swift is to write for low-level systems that need a lot of precise control.

Really? As I do a lot of work in small embedded systems, I'd certainly love to be able to use Swift down at that level. Rust seems to be much farther along in this regard than Swift, I've observed (with some envy).

---

<div class="post-metadata">

**Author:** ![David\_Smith](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/david_smith/32/37508_2.png) [@David\_Smith](https://forums.swift.org/u/David_Smith)\
**Post date:** [May 31, 2023, 2:47am UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/19 "2023-05-31T02:47:02Z")

</div>

An example of this that shipped last year is Swift in the iPhone's Secure Enclave, as mentioned in [What's new in Swift - WWDC22 - Videos - Apple Developer](https://developer.apple.com/videos/play/wwdc2022/110354/)

---

<div class="post-metadata">

**Author:** ![Ling\_Wang](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ling_wang/32/2691_2.png) [@Ling\_Wang](https://forums.swift.org/u/Ling_Wang)\
**Post date:** [December 11, 2024, 4:03pm UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/20 "2024-12-11T16:03:59Z")

</div>

Where can I find the doc of `@convention`? I remember seeing it in the official Swift doc before but couldn't find it anymore.

---

<div class="post-metadata">

**Author:** ![Alex\_Martini](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/alex_martini/32/1383_2.png) [@Alex\_Martini](https://forums.swift.org/u/Alex_Martini)\
**Post date:** [December 11, 2024, 5:56pm UTC](https://forums.swift.org/t/convention-thin-function-pointers/65180/21 "2024-12-11T17:56:04Z")

</div>

This forum thread pitched making `@convention(thin)` an official feature, but I'm not finding anywhere that it was proposed on Swift Evolution or approved. It appears only once in the SE repository, in SE-0426, in a list of types of functions that can conform to `BitwiseCopyable`.

The Swift book has reference for [`@convention`](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/attributes#convention) and notes in a hidden comment that `@convention(thin)` is private, even though it isn't underscored, with a link back to [a forum thread](https://forums.swift.org/t/use-of-convention-thin-to-avoid-allocating-closures/12087/6) from 2018.
