Skip to content

Commit 576155e

Browse files
authored
Merge pull request #2432 from atrick/fix-se-0390-noncopyable
Update SE-0390: Noncopyable structs and enums; Deinitialization
2 parents ab810dd + 4732446 commit 576155e

File tree

1 file changed

+75
-40
lines changed

1 file changed

+75
-40
lines changed

proposals/0390-noncopyable-structs-and-enums.md

Lines changed: 75 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -968,74 +968,109 @@ func foo() {
968968

969969
A noncopyable struct or enum may declare a `deinit`, which will run
970970
implicitly when the lifetime of the value ends (unless explicitly suppressed
971-
as noted below):
971+
with `discard` as explained below):
972972

973973
```swift
974-
struct FileDescriptor: ~Copyable {
975-
private var fd: Int32
974+
struct File: ~Copyable {
975+
var descriptor: Int32
976+
977+
func write<S: Sequence>(_ values: S) { /*..*/ }
978+
979+
consuming func close() {
980+
print("closing file")
981+
}
976982

977983
deinit {
978-
close(fd)
984+
print("deinitializing file")
985+
closeFile(rawDescriptor: descriptor)
979986
}
980987
}
981988
```
982989

983-
Like a class `deinit`, a struct or enum `deinit` may not propagate any uncaught
984-
errors. `self` behaves as in a `borrowing` method; it may not be
985-
modified or consumed by the body of `deinit`. (Allowing for mutation and
986-
partial invalidation inside a `deinit` is explored as a future direction.)
990+
Like a class `deinit`, a struct or enum `deinit` may not propagate any
991+
uncaught errors. Within the body of the `deinit`, `self` behaves as in
992+
a `borrowing` method; it may not be modified or consumed inside the
993+
`deinit`. (Allowing for mutation and partial invalidation inside a
994+
`deinit` is explored as a future direction.)
987995

988996
A value's lifetime ends, and its `deinit` runs if present, in the following
989997
circumstances:
990998

991-
- For a local `var` or `let` binding, or `consuming` function parameter, that
992-
is not itself consumed, `deinit` runs after the last non-consuming use.
993-
If, on the other hand, the binding is consumed, then responsibility for
994-
deinitialization gets forwarded to the consumer (which may in turn forward
995-
it somewhere else).
999+
- For a local `var` or `let` binding, or `consuming` function parameter, that is
1000+
not itself consumed, `deinit` runs at the end of the binding's lexical
1001+
scope. If, on the other hand, the binding is consumed, then responsibility
1002+
for deinitialization gets forwarded to the consumer (which may in turn forward
1003+
it somewhere else). As explained later, a `_ = consume` operator with no
1004+
destination immediately runs the `deinit`.
9961005

9971006
```swift
9981007
do {
999-
var x = FileDescriptor(42)
1000-
1001-
x.close() // consuming use
1002-
// x's deinit doesn't run here (but might run inside `close`)
1008+
let file = File(descriptor: 42)
1009+
file.close() // consuming use
1010+
// file's deinit runs inside `close`
1011+
print("done writing")
10031012
}
1004-
1013+
// Output:
1014+
// closing file
1015+
// deinitializing file
1016+
// done writing
1017+
10051018
do {
1006-
var x = FileDescriptor(42)
1007-
x.write([1,2,3]) // borrowing use
1008-
// x's deinit runs here
1009-
1019+
let file = File(descriptor: 42)
1020+
file.write([1,2,3]) // borrowing use
10101021
print("done writing")
1022+
// file's deinit runs here
10111023
}
1024+
// Output:
1025+
// done writing
1026+
// deinitializing file
10121027
```
10131028

1014-
- When a `struct`, `enum`, or `class` contains a member of noncopyable type,
1015-
the member is destroyed, and its `deinit` is run, after the container's
1016-
`deinit` if any runs.
1029+
If a noncopyable value is conditionally consumed, then the deinitializer
1030+
runs as late as possible on any nonconsumed paths:
10171031

10181032
```swift
1019-
struct Inner: ~Copyable {
1020-
deinit { print("destroying inner") }
1021-
}
1022-
1023-
struct Outer: ~Copyable {
1024-
var inner = Inner()
1025-
deinit { print("destroying outer") }
1026-
}
1027-
1033+
let condition = false
10281034
do {
1029-
_ = Outer()
1035+
let file = File(descriptor: 42)
1036+
file.write([1,2,3]) // borrowing use
1037+
if condition {
1038+
file.close()
1039+
} else {
1040+
print("not closed")
1041+
// file's deinit runs here
1042+
}
1043+
print("done writing")
10301044
}
1045+
// Output:
1046+
// not closed
1047+
// deinitializing file
1048+
// done writing
10311049
```
10321050

1033-
will print:
1051+
- When a struct, enum, or class contains a member of noncopyable type, the member is destroyed, and its deinit is
1052+
run, after the container's deinit runs. For example:
10341053

1035-
```swift
1036-
destroying outer
1037-
destroying inner
1038-
```
1054+
```swift
1055+
struct Inner: ~Copyable {
1056+
deinit { print("destroying inner") }
1057+
}
1058+
1059+
struct Outer: ~Copyable {
1060+
var inner = Inner()
1061+
deinit { print("destroying outer") }
1062+
}
1063+
1064+
do {
1065+
_ = Outer()
1066+
}
1067+
```
1068+
1069+
will print:
1070+
```
1071+
destroying outer
1072+
destroying inner
1073+
```
10391074

10401075
### Suppressing `deinit` in a `consuming` method
10411076

0 commit comments

Comments
 (0)