Skip to content

Commit 7dd890e

Browse files
committed
Revisions in response to pitch discussion:
- Use `taking` and `borrowing func` modifiers to control `self`'s convention - Add "alternative considered" for whether `take`n parameters should be bound as mutable in the callee
1 parent ab25cbd commit 7dd890e

File tree

1 file changed

+84
-37
lines changed

1 file changed

+84
-37
lines changed

proposals/NNNN-parameter-ownership-modifiers.md

Lines changed: 84 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -88,43 +88,54 @@ declarations. They can appear in the same places as the `inout` modifier, and
8888
are mutually exclusive with each other and with `inout`. In a `func`,
8989
`subscript`, or `init` declaration, they appear as follows:
9090

91-
```
91+
```swift
9292
func foo(_: borrow Foo)
9393
func foo(_: take Foo)
9494
func foo(_: inout Foo)
9595
```
9696

9797
In a closure:
9898

99-
```
99+
```swift
100100
bar { (a: borrow Foo) in a.foo() }
101101
bar { (a: take Foo) in a.foo() }
102102
bar { (a: inout Foo) in a.foo() }
103103
```
104104

105105
In a function type:
106106

107-
```
107+
```swift
108108
let f: (borrow Foo) -> Void = { a in a.foo() }
109109
let f: (take Foo) -> Void = { a in a.foo() }
110110
let f: (inout Foo) -> Void = { a in a.foo() }
111111
```
112112

113-
TODO: How to apply these modifiers to `self`, the `newValue` of accessors, ...
113+
Methods can using the `taking` or `borrowing` modifier to indicate that they
114+
take ownership of their `self` parameter, or they borrow it. These modifiers
115+
are mutually exclusive with each other and with the existing `mutating` modifier:
116+
117+
```swift
118+
struct Foo {
119+
taking func foo() // `take` ownership of self
120+
borrowing func foo() // `borrow` self
121+
mutating func foo() // modify self with `inout` semantics
122+
}
123+
```
114124

115125
`take` or `borrow` on a parameter do not affect the caller-side syntax for
116-
passing an argument to the affected declaration. For typical Swift code,
117-
adding, removing, or changing these modifiers does not have any source-breaking
118-
effects. (See "related directions" below for interactions with other language
119-
features being considered currently or in the near future which might interact
120-
with these modifiers in ways that cause them to break source.)
126+
passing an argument to the affected declaration, nor do `taking` or
127+
`borrowing` affect the application of `self` in a method call. For typical
128+
Swift code, adding, removing, or changing these modifiers does not have any
129+
source-breaking effects. (See "related directions" below for interactions with
130+
other language features being considered currently or in the near future which
131+
might interact with these modifiers in ways that cause them to break source.)
121132

122133
Protocol requirements can also use `take` and `borrow`, and the modifiers will
123134
affect the convention used by the generic interface to call the requirement.
124135
The requirement may still be satisfied by an implementation that uses different
125136
conventions for parameters of copyable types:
126137

127-
```
138+
```swift
128139
protocol P {
129140
func foo(x: take Foo, y: borrow Foo)
130141
}
@@ -148,7 +159,7 @@ Function values can also be implicitly converted to function types that change
148159
the convention of parameters of copyable types among unspecified, `borrow`,
149160
or `take`:
150161

151-
```
162+
```swift
152163
let f = { (a: Foo) in print(a) }
153164

154165
let g: (borrow Foo) -> Void = f
@@ -191,6 +202,61 @@ changing these annotations to an API should not affect its existing clients.
191202

192203
## Alternatives considered
193204

205+
### Making `take` parameter bindings mutable inside the callee
206+
207+
It is likely to be common for functions that `take` ownership of their
208+
parameters to want to modify the value of the parameter they received. Taking
209+
ownership of a COW value type allows for a caller with the only reference to
210+
a COW buffer to transfer ownership of that unique reference, and the callee
211+
can then take advantage of that ownership to do in-place mutation of the
212+
parameter, allowing for efficiency while still presenting a "pure" functional
213+
interface externally:
214+
215+
```swift
216+
extension String {
217+
// Append `self` to another String, using in-place modification if
218+
// possible
219+
taking func plus(_ other: String) {
220+
// Transfer ownership of the `self` parameter to a mutable variable
221+
var myself = take self
222+
// Modify it in-place, taking advantage of uniqueness if possible
223+
myself += other
224+
return myself
225+
}
226+
}
227+
228+
// This is amortized O(n) instead of O(n^2)!
229+
let helloWorld = "hello ".plus("cruel ").plus("world")
230+
```
231+
232+
If this is common enough, we could consider making it so that the parameter
233+
binding inside a function body for a `take` parameter, or for the `self`
234+
parameter of a `taking func`, is mutable out of the gate, removing the need
235+
to reassign it to a local `var`:
236+
237+
```swift
238+
extension String {
239+
// Append `self` to another String, using in-place modification if
240+
// possible
241+
taking func plus(_ other: String) -> String {
242+
// Modify it in-place, taking advantage of uniqueness if possible
243+
self += other
244+
return self
245+
}
246+
}
247+
```
248+
249+
This does make changing a `take` parameter to `borrow`, or removing the
250+
`take` annotation from a parameter, potentially source-breaking, but in a
251+
purely localized way, since the parameter binding inside the function would
252+
only become immutable again. There is also still the potential for confusion
253+
from users who mutate parameters within the function and expect those mutations
254+
to persist in the caller, which is part of why we removed the ability to declare
255+
a parameter `var` from early versions of Swift. This might be less of a concern
256+
when using `take` with move-only types, since without the ability for the caller
257+
to copy its argument, there's no way for the caller to see the argument after
258+
the callee takes it and modifies it.
259+
194260
### Naming
195261

196262
We have considered alternative naming schemes for these modifiers:
@@ -203,7 +269,9 @@ We have considered alternative naming schemes for these modifiers:
203269
found that the "shared" versus "exclusive" language for discussing borrows,
204270
while technically correct, is unnecessarily confusing for explaining the
205271
model.
206-
- A previous pitch used the names `nonconsuming` and `consuming`.
272+
- A previous pitch used the names `nonconsuming` and `consuming`. The current
273+
implementation also uses `__consuming func` to notate a method that takes
274+
ownership of its `self` parameter.
207275

208276
The names `take` and `borrow` arose during [the first review of
209277
SE-0366](https://forums.swift.org/t/se-0366-move-function-use-after-move-diagnostic/59202).
@@ -237,27 +305,6 @@ implicitly copyable values), and that explicit operators on the call site
237305
can be used in the important, but relatively rare, cases where the default
238306
optimizer behavior is insufficient to get optimal code.
239307

240-
### Applying `borrow` and `take` modifiers to the `self` parameter of methods
241-
242-
This proposal does not yet specify how to control the calling convention of
243-
the `self` parameter for methods. As currently implemented, the `__consuming`
244-
modifier can be applied to the method declaration to make `self` be taken,
245-
similar to how the `mutating` method modifier makes `self` be `inout`. We could
246-
continue that scheme with new function-level modifiers:
247-
248-
```
249-
struct Foo {
250-
mutating func mutate() // self is inout
251-
taking func take() // self is take
252-
borrowing func borrow() // self is borrow
253-
}
254-
```
255-
256-
Alternatively, we could explore schemes to [allow the `self` parameter to be
257-
declared explicitly](https://forums.swift.org/t/optional-explicit-self-parameter-declaration-in-methods/59522), which would allow for the `take` and `borrow`
258-
modifiers as proposed for other parameters to also be applied to an explicit
259-
`self` parameter declaration.
260-
261308
## Related directions
262309

263310
### `take` operator
@@ -271,7 +318,7 @@ last use, without depending on optimization and vague ARC optimizer rules.
271318
When the lifetime of the variable ends in an argument to a `take` parameter,
272319
then we can transfer ownership to the callee without any copies:
273320

274-
```
321+
```swift
275322
func consume(x: take Foo)
276323

277324
func produce() {
@@ -293,7 +340,7 @@ instead of passing a copy, then the mutation of `global` inside of `useFoo`
293340
would trigger a dynamic exclusivity failure (or UB if exclusivity checks
294341
are disabled):
295342

296-
```
343+
```swift
297344
var global = Foo()
298345

299346
func useFoo(x: borrow Foo) {
@@ -325,7 +372,7 @@ not attempt to modify the same object or global variable during a call,
325372
and want to suppress this copy. An explicit `borrow` operator could allow for
326373
this:
327374

328-
```
375+
```swift
329376
var global = Foo()
330377

331378
func useFooWithoutTouchingGlobal(x: borrow Foo) {
@@ -354,7 +401,7 @@ take a value destroy it and prevent its continued use. This makes the
354401
convention used for move-only parameters a much more important part of their
355402
API contract:
356403

357-
```
404+
```swift
358405
moveonly struct FileHandle { ... }
359406

360407
// Operations that open a file handle return new FileHandle values

0 commit comments

Comments
 (0)