deinit
works on swift-5.9-DEVELOPMENT-SNAPSHOT-2023-06-05
on Ubuntu. Maybe try to download the latest toolchain from swift.org? Then just plug it in Xcode.
Btw. If you expect to see deinit called
in console then you need to do following:
public struct SomeStruct: ~Copyable {
public let val: Int
public init(val: Int) {
self.val = val
}
deinit {
debugPrint("deinit called")
}
}
func test() { // <--------- HERE
let val = SomeStruct(val: 6)
print(val.val)
}
test()
Your object lives in the global scope, if you are just testing the lifetimes then create an artificial scope, so that your object does not live forever. You can also use do this:
do {
let val = SomeStruct(val: 6)
print(val.val)
}
But this is more fancy, not a lot of people know about the do
scoping.
Question
(This is possibly a duplicate, because it is the 1st thing you check with move-only. If that’s the case then just say so, don't waste your time on writing an answer.)
What would you expect this code to do:
struct Alice: ~Copyable {
var age: Int
init(age: Int) {
print("INIT");
self.age = age
}
deinit { print("DEINIT") }
}
func eatMe(_ alice: consuming Alice) {
print(" start")
print(" age:", alice.age)
print(" end")
}
// In some function, you can't have it in global scope.
let alice = Alice(age: 10)
eatMe(alice)
Possible options (column-wise):
|
A |
B |
C |
Stdout |
INIT start DEINIT age: 10 end |
INIT start age: 10 DEINIT end |
INIT start age: 10 end DEINIT |
Why? |
tmp = alice.age
alice.deinit()
print(" age:", tmp)
|
print(" age:", alice.age) is the last proper usage of alice , so deinit should be after it. |
eatMe takes an ownership of alice , so the lifetime is bound to function execution. You may expect this if you come from the other language. |
A, B or C? Try to answer it without thinking about the compiler, just looking at the code.
Answer
Answer: A (swift-5.9-DEVELOPMENT-SNAPSHOT-2023-06-05
).
So, basically Swift goes for the minimal lifetime. This is teknikly the most correct answer. It can even save your bacon if you are dealing with many file descriptors at the same time (though your code is still not correct if you rely on the exact position where deinit
will be called).
Tbh. I thought it would be B. For most of the programmers print(" age:", alice.age)
is the last usage. With current implementation it kind of looks like the argument alice
was deallocated before the print
call (though alice
was never an argument, just their age
). It is counterintuitive, or at least for me it was a bit surprising.
Other types (does not really matter)
Just for the reference this is how it works for other types (this does not really matter, consuming
is an entirely different beast than those below, this is just for completeness):
|
Struct +
borrowing argument |
Struct +
inout argument |
Class |
Stdout |
INIT start age: 10 end DEINIT |
INIT start age: 10 end DEINIT |
INIT start age: 10 end DEINIT |
Why? |
Obviously. |
Same as borrowing . |
Conceptually Swift retains the argument, before function call. |
Other remarks
Anyway, I will be adding move-only types as smart pointers in Violet - Python VM written in Swift . The whole code was already written with this in mind (2 years ago): Python object is just a struct
with an pointer inside, with move-only types we can reference count + explicit retain
to copy
. Reference cycles are annoying to deal with, so let them be.
Other than the question above the move-only series of proposals is almost exactly what I expected (which is a good thing), so I don't have any more remarks.
Other major pain points (they will be fixed eventually, but they are still annoying):
-
No protocol conformances. Workaround:
// Swift does not support protocol conformances on `~Copyable` types (except for Sendable).
public typealias PyObjectMixin = Sendable
Then generate the needed code via Python script (or macros or Sourcery).
-
No std lib