|
| 1 | +# Formally defining `consuming` and `nonconsuming` argument type modifiers |
| 2 | + |
| 3 | +* Proposal: [SE-NNNN](NNNN-consuming-nonconsuming.md) |
| 4 | +* Authors: [Michael Gottesman](https://github.com/gottesmm), [Andrew Trick](https://github.com/atrick) |
| 5 | +* Review Manager: TBD |
| 6 | +* Status: **Awaiting implementation** |
| 7 | +* Pitch v1: [https://github.com/gottesmm/swift-evolution/blob/consuming-nonconsuming-pitch-v1/proposals/000b-consuming-nonconsuming.md](https://github.com/gottesmm/swift-evolution/blob/consuming-nonconsuming-pitch-v1/proposals/000b-consuming-nonconsuming.md) |
| 8 | + |
| 9 | +<!-- |
| 10 | +*During the review process, add the following fields as needed:* |
| 11 | + |
| 12 | +* Implementation: [apple/swift#NNNNN](https://github.com/apple/swift/pull/NNNNN) or [apple/swift-evolution-staging#NNNNN](https://github.com/apple/swift-evolution-staging/pull/NNNNN) |
| 13 | +* Decision Notes: [Rationale](https://forums.swift.org/), [Additional Commentary](https://forums.swift.org/) |
| 14 | +* Bugs: [SR-NNNN](https://bugs.swift.org/browse/SR-NNNN), [SR-MMMM](https://bugs.swift.org/browse/SR-MMMM) |
| 15 | +* Previous Revision: [1](https://github.com/apple/swift-evolution/blob/...commit-ID.../proposals/NNNN-filename.md) |
| 16 | +* Previous Proposal: [SE-XXXX](XXXX-filename.md) |
| 17 | +--> |
| 18 | + |
| 19 | +## Introduction |
| 20 | + |
| 21 | +By default the Swift compiler uses simple heuristics to determine whether a |
| 22 | +function takes ownership of its arguments. In some cases, these heuristics |
| 23 | +result in compiled code that forces the caller or callee to insert unnecessary |
| 24 | +copies and destroys. We propose new `consuming` and `nonconsuming` argument type |
| 25 | +modifiers to allow developers to override said compiler heuristics and |
| 26 | +explicitly chose the convention used by the compiler when writing performance |
| 27 | +sensitive code. |
| 28 | + |
| 29 | +Pitch thread: [https://forums.swift.org/t/pitch-formally-defining-consuming-and-nonconsuming-argument-type-modifiers](https://forums.swift.org/t/pitch-formally-defining-consuming-and-nonconsuming-argument-type-modifiers) |
| 30 | + |
| 31 | +Swift-evolution thread: [Discussion thread topic for that proposal](https://forums.swift.org/) |
| 32 | + |
| 33 | +## Motivation |
| 34 | + |
| 35 | +In Swift, all non-trivial function arguments use one of two conventions that |
| 36 | +specify if a caller or callee function is responsible for managing the |
| 37 | +argument's lifetime. The two conventions are: |
| 38 | + |
| 39 | +* **`consuming`**. The caller function is transferring ownership of a value to |
| 40 | + the callee function. The callee then becomes responsible for managing the |
| 41 | + lifetime of the value. Semantically, this is implemented by requiring the |
| 42 | + caller to emit an unbalanced retain upon the value that then must be balanced |
| 43 | + by a consuming operation in the callee. This unbalanced retain causes such an |
| 44 | + operation to be called "passing the argument at +1". |
| 45 | + |
| 46 | +* **`nonconsuming`**. The caller function is lending ownership of a value to the |
| 47 | + callee. The callee does not own the value and must retain the value to consume |
| 48 | + it (e.x.: passing as a consuming argument). A reference counted `nonconsuming` |
| 49 | + argument is called a "+0 argument" since it is passed without emitting an |
| 50 | + unbalanced retain (in contrast to a "+1 argument") and all retain/release pairs |
| 51 | + are properly balanced locally within the caller/callee rather than over the |
| 52 | + call boundary. |
| 53 | + |
| 54 | +By default Swift chooses which convention to use based upon the function type of |
| 55 | +the callee as well as the position of the argument in the callee's argument |
| 56 | +list. Specifically: |
| 57 | + |
| 58 | +1. If a callee is an initializer, then an argument is always passed as |
| 59 | + `consuming`. |
| 60 | + |
| 61 | +2. If a callee is a setter, then an argument is passed as `consuming` if the |
| 62 | + argument is a non-self argument. |
| 63 | + |
| 64 | +3. Otherwise regardless of the callee type, an argument is always passed as |
| 65 | + `nonconsuming`. |
| 66 | + |
| 67 | +Over all, these defaults been found to work well, but in performance sensitive |
| 68 | +situations an API designer may need to customize these defaults to eliminate |
| 69 | +unnecessary copies and destroys. Despite that need, today there does not exist |
| 70 | +source stable Swift syntax for customizing those defaults. |
| 71 | + |
| 72 | +## Motivating Examples |
| 73 | + |
| 74 | +Despite the lack of such source stable syntax, to support the stdlib, the |
| 75 | +compiler has for some time provided underscored, source unstable keywords that |
| 76 | +allowed stdlib authors to override the default conventions: |
| 77 | + |
| 78 | +1. `__shared`. This is equivalent to `nonconsuming`. |
| 79 | +2. `__owned`. This is equivalent to `consuming`. |
| 80 | +3. `__consuming`. This is used to have methods take self as a `consuming` argument. |
| 81 | + |
| 82 | +Here are some examples of situations where developers have found it necessary to |
| 83 | +use these underscored attributes to eliminate overhead caused by using the |
| 84 | +default conventions: |
| 85 | + |
| 86 | +* Passing a non-consuming argument to an initializer or setter if one is going |
| 87 | + to consume a value derived from the argument instead of the argument itself. |
| 88 | + |
| 89 | + 1. [String initializer for Substring](https://github.com/apple/swift/blob/09507f59cf36e83ebc2d1d1ab85cba8f4fc2e87c/stdlib/public/core/Substring.swift#L22). This API uses the underscored API `__shared` since semantically the author's want to create a String that is a copy of the substring. Since the Substring itself is not being consumed, without __shared we would have additional ref count traffic. |
| 90 | + ```swift |
| 91 | + extension String { |
| 92 | + /// Creates a new string from the given substring. |
| 93 | + /// |
| 94 | + /// - Parameter substring: A substring to convert to a standalone `String` |
| 95 | + /// instance. |
| 96 | + /// |
| 97 | + /// - Complexity: O(*n*), where *n* is the length of `substring`. |
| 98 | + @inlinable |
| 99 | + public init(_ substring: __shared Substring) { |
| 100 | + self = String._fromSubstring(substring) |
| 101 | + } |
| 102 | + } |
| 103 | + ``` |
| 104 | + 2. Initializing a cryptographic algorithm state by accumulating over a collection. Example: [ChaCha]( https://github.com/apple/swift/blob/324cccd18e9297b3cea9fc88d1ce80a0debe657e/benchmark/single-source/ChaCha.swift#L59). In this case, the ChaCha state is initialized using the contents of the collection "key" rather than "key" itself. NOTE: One thing to keep in mind with this example is that the optimizer completely inlines away the iterator so even though we use an iterator here. This results in the optimizer eliminating all of the ARC traffic from the usage of the CollectionOf32BitLittleEndianIntegers.makeIterator() causing the only remaining ARC traffic to be related to key being passed as an argument. Hence if we did not use shared, we would have an unnecessary release in init. |
| 105 | + ```swift |
| 106 | + init<Key: Collection, Nonce: Collection>(key: __shared Key, nonce: Nonce, counter: UInt32) where Key.Element == UInt8, Nonce.Element == UInt8 { |
| 107 | + /* snip */ |
| 108 | + var keyIterator = CollectionOf32BitLittleEndianIntegers(key).makeIterator() |
| 109 | + self._state.4 = keyIterator.next()! |
| 110 | + self._state.5 = keyIterator.next()! |
| 111 | + self._state.6 = keyIterator.next()! |
| 112 | + /* snip */ |
| 113 | + } |
| 114 | + ``` |
| 115 | +* Passing a consuming argument to a normal function or method that isn't a |
| 116 | + setter but acts like a setter. |
| 117 | + 1. Implementing append on a collection. Example: [Array.append(_:)](https://github.com/apple/swift/blob/324cccd18e9297b3cea9fc88d1ce80a0debe657e/stdlib/public/core/Array.swift#L1167). In this example, we want to forward the element directly into memory without inserting a retain, so we must use the underscored attribute `__owned` to change the default convention to be consuming. |
| 118 | + ```swift |
| 119 | + public mutating func append(_ newElement: __owned Element) { |
| 120 | + // Separating uniqueness check and capacity check allows hoisting the |
| 121 | + // uniqueness check out of a loop. |
| 122 | + _makeUniqueAndReserveCapacityIfNotUnique() |
| 123 | + let oldCount = _buffer.mutableCount |
| 124 | + _reserveCapacityAssumingUniqueBuffer(oldCount: oldCount) |
| 125 | + _appendElementAssumeUniqueAndCapacity(oldCount, newElement: newElement) |
| 126 | + _endMutation() |
| 127 | + } |
| 128 | + ``` |
| 129 | + 2. Bridging APIs. Example: [_bridgeAnythingNonVerbatimToObjectiveC()](https://github.com/apple/swift/blob/324cccd18e9297b3cea9fc88d1ce80a0debe657e/stdlib/public/core/BridgeObjectiveC.swift#L216). In this case, we want to consume the object into its bridged representation so we do not have to copy when bridging. |
| 130 | + ```swift |
| 131 | + func _bridgeAnythingNonVerbatimToObjectiveC<T>(_ x: __owned T) -> AnyObject |
| 132 | + ``` |
| 133 | +* Consuming self when calling a method that is not an initializer. |
| 134 | + 1. Creating an iterator for a collection. Example: [Collection.makeIterator()](https://github.com/apple/swift/blob/324cccd18e9297b3cea9fc88d1ce80a0debe657e/stdlib/public/core/Collection.swift#L1008). The iterator needs to have a reference to self so to reduce ARC traffic, we pass self into makeIterator at +1. |
| 135 | + ```swift |
| 136 | + extension Collection where Iterator == IndexingIterator<Self> { |
| 137 | + /// Returns an iterator over the elements of the collection. |
| 138 | + @inlinable |
| 139 | + public __consuming func makeIterator() -> IndexingIterator<Self> { |
| 140 | + return IndexingIterator(_elements: self) |
| 141 | + } |
| 142 | + } |
| 143 | + ``` |
| 144 | + 2. Sequence based algorithms that use iterators. Example: [Sequence.filter()](https://github.com/apple/swift/blob/324cccd18e9297b3cea9fc88d1ce80a0debe657e/stdlib/public/core/Sequence.swift#L678). In this case since we are using makeIterator, we need self to be __consuming. |
| 145 | + ```swift |
| 146 | + @inlinable |
| 147 | + public __consuming func filter( |
| 148 | + _ isIncluded: (Element) throws -> Bool |
| 149 | + ) rethrows -> [Element] { |
| 150 | + var result = ContiguousArray<Element>() |
| 151 | + |
| 152 | + var iterator = self.makeIterator() |
| 153 | + |
| 154 | + while let element = iterator.next() { |
| 155 | + if try isIncluded(element) { |
| 156 | + result.append(element) |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + return Array(result) |
| 161 | + } |
| 162 | + ``` |
| 163 | + |
| 164 | +In all of the above cases, by using underscored attributes, authors changed the |
| 165 | +default convention since it introduced extra copy/destroys. |
| 166 | + |
| 167 | +## Proposed solution |
| 168 | + |
| 169 | +As mentioned in the previous section, the compiler already internally supports |
| 170 | +these semantics in the guise of underscored, source unstable keywords `__owned`, |
| 171 | +`__shared` and for self the keyword `__consuming`. We propose that we: |
| 172 | + |
| 173 | +1. Add two new keywords to the language: `consuming` and `nonconsuming`. |
| 174 | + |
| 175 | +2. Make `consuming` a synonym for `__consuming` when using `__consuming` to make |
| 176 | + self a +1 argument. |
| 177 | + |
| 178 | +3. On non-self arguments, make `consuming` a synonym for `__owned` and |
| 179 | + `nonconsuming` a synonym for `__shared`. |
| 180 | + |
| 181 | +## Detailed design |
| 182 | + |
| 183 | +We propose formally modifying the Swift grammar as follows: |
| 184 | + |
| 185 | +``` |
| 186 | +// consuming, nonconsuming for parameters |
| 187 | +- type-annotation → : attributes? inout? type |
| 188 | ++ type-annotation → : attributes? type-modifiers? type |
| 189 | ++ type-modifiers → : type-modifier type-modifier* |
| 190 | ++ type-modifier → : inout |
| 191 | ++ → : consuming |
| 192 | ++ → : nonconsuming |
| 193 | ++ |
| 194 | + |
| 195 | +// consuming for self |
| 196 | ++ declaration-modifier → : consuming |
| 197 | +``` |
| 198 | + |
| 199 | +The only work that is required is to add support to the compiler for accepting |
| 200 | +the new spellings mentioned (`consuming` and `nonconsuming`) for the underscored |
| 201 | +variants of those keywords. |
| 202 | + |
| 203 | +## Source compatibility |
| 204 | + |
| 205 | +Since we are just adding new spellings for things that already exist in the |
| 206 | +compiler, this is additive and there isn't any source compatibility impact. |
| 207 | + |
| 208 | +## Effect on ABI stability |
| 209 | + |
| 210 | +This will not effect the ABI of any existing language features since all uses |
| 211 | +that already use `__owned`, `__shared`, and `__consuming` will work just as |
| 212 | +before. Applying `consuming`, `nonconsuming` to function arguments will result |
| 213 | +in ABI break to existing functions if the specified convention does not match |
| 214 | +the default convention. |
| 215 | + |
| 216 | +## Effect on API resilience |
| 217 | + |
| 218 | +Changing a argument from `consuming` to `nonconsuming` or vice versa is an |
| 219 | +ABI-breaking change. Adding an annotation that matches the default convention |
| 220 | +does not change the ABI. |
| 221 | + |
| 222 | +## Alternatives considered |
| 223 | + |
| 224 | +We could reuse `owned` and `shared` and just remove the underscores. This was |
| 225 | +viewed as confusing since `shared` is used in other contexts since `shared` can |
| 226 | +mean a rust like "shared borrow" which is a much stronger condition than |
| 227 | +`nonconsuming` is. Additionally, since we already will be using `consuming` to |
| 228 | +handle +1 for self, for consistency it makes sense to also rename `owned` to |
| 229 | +`consuming`. |
| 230 | + |
| 231 | +## Acknowledgments |
| 232 | + |
| 233 | +Thanks to Robert Widmann for the original underscored implementation of |
| 234 | +`__owned` and `__shared`: [https://forums.swift.org/t/ownership-annotations/11276](https://forums.swift.org/t/ownership-annotations/11276). |
0 commit comments