Skip to content

Commit aa266d2

Browse files
authored
SE-0432: Respond to review feedback. (#2428)
Subset out `borrowing` bindings, and mention them as a future direction in tandem with `borrowing` bindings appearing in other places in the language. Make it clear that using a dynamic cast pattern would require the switch to already be consuming the subject, rather than having the effect of turning the entire switch into a borrowing switch implicitly.
1 parent 6df26d7 commit aa266d2

File tree

1 file changed

+14
-154
lines changed

1 file changed

+14
-154
lines changed

proposals/0432-noncopyable-switch.md

Lines changed: 14 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -65,21 +65,6 @@ expression:
6565
of borrowing.)
6666
- Otherwise, the baseline mode is *consuming*.
6767

68-
While looking through the patterns:
69-
70-
- if there is a `borrowing` binding subpattern (described below), then the
71-
`switch` behavior is at least *borrowing*.
72-
- if there is a `var` binding subpattern, and the subpattern is of
73-
a noncopyable type, then the `switch` behavior is *consuming*. If the
74-
subpattern is copyable, then `var` bindings do not affect the behavior
75-
of the `switch`, since the binding value can be copied if necessary to
76-
form the binding.
77-
- if there is an `as T` subpattern, and the type of the value being matched
78-
is noncopyable, then the `switch` behavior is *consuming*. If the value
79-
being matched is copyable, there is no effect on the behavior of the
80-
`switch`. This is because some forms of dynamic cast on noncopyable types
81-
may require consuming the input value.
82-
8368
For example, given the following copyable definition:
8469

8570
```
@@ -93,15 +78,8 @@ then the following patterns have ownership behavior as indicated below:
9378

9479
```
9580
case let x: // copying
96-
case borrowing x: // borrowing
97-
9881
case .foo(let x): // copying
99-
case .foo(borrowing x): // borrowing
100-
10182
case .bar(let x, let y): // copying
102-
case .bar(borrowing x, let y): // borrowing
103-
case .bar(let x, borrowing y): // borrowing
104-
case .bar(borrowing x, borrowing y): // borrowing
10583
```
10684

10785
And for a noncopyable enum definition:
@@ -122,26 +100,18 @@ var foo: NoncopyableEnum // stored variable
122100
123101
switch foo {
124102
case let x: // borrowing
125-
case borrowing x: // borrowing
126103
127104
case .copyable(let x): // borrowing (because `x: Int` is copyable)
128-
case .copyable(borrowing x): // borrowing
129105
130106
case .noncopyable(let x): // borrowing
131-
case .noncopyable(borrowing x): // borrowing
132107
}
133108
134109
func bar() -> NoncopyableEnum {...} // function returning a temporary
135110
136111
switch bar() {
137112
case let x: // consuming
138-
case borrowing x: // borrowing
139-
140113
case .copyable(let x): // borrowing (because `x: Int` is copyable)
141-
case .copyable(borrowing x): // borrowing
142-
143114
case .noncopyable(let x): // consuming
144-
case .noncopyable(borrowing x): // borrowing
145115
}
146116
```
147117

@@ -229,133 +199,12 @@ including casts that bridge or which wrap the value in an existential
229199
container, need to consume or copy parts of the input value in order to form
230200
the result. The cast can still be separated into a check whether the type
231201
matches, using a borrowing access, followed by constructing the actual cast
232-
result by consuming if necessary. However, for this to be allowed, the
202+
result by consuming if necessary. To do this, the switch would have already
203+
be a consuming switch. But also, for a consuming `as T` pattern to work, the
233204
subpattern `p` of the `p as T` pattern would need to be irrefutable, and the
234205
pattern could not have an associated `where` clause, since we would be unable
235206
to back out of the pattern match once a consuming cast is performed.
236207

237-
### `borrowing` bindings
238-
239-
In order to explicitly declare a binding as `borrowing`,
240-
we introduce a new `borrowing` binding modifier in patterns. A `borrowing`
241-
binding references the matched part of the value as it currently exists in the
242-
value from the pattern match without copying it, instead putting the subject
243-
under a *borrowing access* in order to access the matched part. Like other
244-
borrow bindings, the borrowed value cannot be consumed or mutated.
245-
246-
```
247-
var x: MyNCEnum = ...
248-
switch x {
249-
case .foo(borrowing y):
250-
// `y` is now borrowed directly out of `x`. This means we can access it
251-
// borrowing operations:
252-
y.access()
253-
254-
// and we can still access `x` with borrowing operations as well:
255-
x.doStuff()
256-
257-
// However, we can't consume `y` or extend its lifetime beyond the borrow
258-
Task.detached {
259-
y.access() // error, can't capture borrow `y` in an escaping closure
260-
}
261-
y.close() // error, can't consume `y`
262-
263-
// And we also can't consume or modify `x` while `y` is borrowed out of it
264-
x = .foo(Handle(value: 42)) // error, can't modify x while borrowed
265-
x.throwAway() // error, can't consume x while borrowed
266-
}
267-
268-
// And now `x` was only borrowed by the `switch`, so we can continue using
269-
// it afterward
270-
x.doStuff()
271-
x.throwAway()
272-
x = .foo(Handle(value: 1738))
273-
274-
// Even if we `consume x` in the switch subject, a `borrowing` binding cannot
275-
// be locally consumed.
276-
switch consume x {
277-
case .foo(borrowing y):
278-
y.access()
279-
280-
x.doStuff()
281-
282-
// Even though we consumed `x`, `y` is still only a borrow binding so
283-
// can't consume or extend its lifetime.
284-
Task.detached {
285-
y.access() // error, can't capture borrow `y` in an escaping closure
286-
}
287-
y.close() // error, can't consume `y`
288-
}
289-
290-
```
291-
292-
`borrowing` bindings can also be formed when the subject of the pattern
293-
match and/or the subpattern have `Copyable` types. Like `borrowing` parameter
294-
bindings, a `borrowing` pattern binding is not implicitly copyable in the
295-
body of the `case`, but can be explicitly copied using the `copy` operator.
296-
297-
```
298-
var x: MyCopyableEnum = ...
299-
300-
switch x {
301-
case .foo(borrowing y):
302-
// We can use `y` in borrowing ways.
303-
304-
// But we can't implicitly extend its lifetime or perform consuming
305-
// operations on it, since those would need to copy
306-
var myString = "hello"
307-
myString.append(y) // error, consumes `y`
308-
Task.detached {
309-
print(y) // error, can't extend lifetime of borrow without copying
310-
}
311-
312-
// Explicit copying makes it ok
313-
Task.detached {[y = copy y] in
314-
print(y)
315-
}
316-
myString.append(copy y)
317-
318-
// `x` is still copyable, so we can update it independently without
319-
// disturbing `y`
320-
x.doStuff()
321-
x = MyEnum.foo("38")
322-
323-
}
324-
```
325-
326-
To maintain source compatibility, `borrowing` is parsed as a contextual
327-
keyword only when it appears immediately before an identifier name. In
328-
other positions, it parses as a declaration reference as before, forming
329-
an enum case pattern or expression pattern depending on what the name
330-
`borrowing` refers to.
331-
332-
```
333-
switch y {
334-
case borrowing(x): // parses as an expression pattern
335-
...
336-
case borrowing(let x): // parses as an enum case pattern binding `x` as a let
337-
...
338-
case borrowing.foo(x): // parses as an expression pattern
339-
...
340-
case borrowing.foo(let x): // parses as an enum case pattern binding `x` as a let
341-
...
342-
case borrowing x: // parses as a pattern binding `x` as a borrow
343-
...
344-
case borrowing(borrowing x) // parses as an enum case pattern binding `x` as a borrow
345-
...
346-
}
347-
```
348-
349-
This does mean that, unlike `let` and `var`, `borrowing` cannot be applied
350-
over a compound pattern to mark all of the identifiers in the subpatterns
351-
as bindings.
352-
353-
```
354-
case borrowing .foo(x, y): // parses as `borrowing.foo(x, y)`, a method call expression pattern
355-
356-
case borrowing (x, y): // parses as `borrowing(x, y)`, a function call expression pattern
357-
```
358-
359208
### `case` conditions in `if`, `while`, `for`, and `guard`
360209

361210
Patterns can also appear in `if`, `while`, `for`, and `guard` forms as part
@@ -446,11 +295,22 @@ temporary, as in:
446295
let x: String? = "hello"
447296
448297
switch borrow x {
449-
case .some(borrowing y): // ensure y is bound from a borrow of x, no copies
298+
case .some(let y): // ensure y is bound from a borrow of x, no copies
450299
...
451300
}
452301
```
453302

303+
### `borrowing` bindings in patterns
304+
305+
In the future, we want to support `borrowing` and `inout` local bindings
306+
in functions and potentially even as fields in nonescapable types. It might
307+
also be useful to specify explicitly `borrowing` bindings within patterns.
308+
Although the default behavior for a `let` binding within a noncopyable
309+
borrowing `switch` pattern is to borrow the matched value, an explicitly
310+
`borrowing` binding could be used to indicate that a copyable binding should
311+
have its local implicit copyability suppressed, like a `borrowing` parameter
312+
binding.
313+
454314
## Alternatives considered
455315

456316
### Determining pattern match ownership wholly from patterns

0 commit comments

Comments
 (0)