Sketch: Teach init a 'defer'-like ability to deinit


(Jon Hull) #1

I really love this idea. My mental model of it is that it is exactly like ‘defer’, except it works on the lifetime of the object instance instead of a function/method. Same thing, different scope.

I like how the creation and destruction are right next to one another. It also solves a lot of potential issues with partial initialization, I believe.

I might spell it ‘deferToDeinit’ or 'deferUntilDeinit'

The only issue I see is accidentally capturing self strongly. Is there a way to mark a closure as implicitly unowned self so the end programmer doesn’t have to worry about it?

Thanks,
Jon

···

Twitter tl;dr:
> Brent: So each instance must remember which init was used for it and then run the matching deinit code at deinit time?
> Me: In my version, the constructive act and destructive act are always paired, even redundantly, using a stack if needed
> Graham: so all your deferredDeinit blocks would run, no matter which init was invoked?
> Brent: Closure stack in the worst case. Might be able to optimize to something cheaper if no captures. Degenerate case: `for i in 0..<10 { deinit { print(i) }

So continuing on from Twitter, assuming the compiler cannot optimize in the case of multiple inits, and init-redirections, how about allowing traditional deinit as well, and introduce compile-time optimization into traditional de-init if the compiler finds only one initialization path per class? We can also warn anyone using my version in a complicated degenerate way that it can be costly through education, manual, etc. It would also help if (especially in Cocoa), you could legally use shared initialization setup closures.

If I create an observer, I want to be able to handle its end-of-life at that point. If I allocate memory, ditto. Etc etc. Surely Swift should be able to support doing this.

-- E

> On Jun 8, 2016, at 3:43 PM, Erica Sadun via swift-evolution <swift-evolution at swift.org <https://lists.swift.org/mailman/listinfo/swift-evolution>> wrote:
>
> I really like this idea. Spatially moving cleanup next to unsafe operations is good practice.
>
> In normal code, I want my cleanup to follow as closely as possible to my unsafe act:
>
> let buffer: UnsafeMutablePointer<CChar> = UnsafeMutablePointer(allocatingCapacity: chunkSize)
> defer { buffer.deallocateCapacity(chunkSize) }
>
> (Sorry for the horrible example, but it's the best I could grep up with on a moment's notice)
>
> I like your idea but what I want to see is not the deinit child closure in init you propose but a new keyword that means defer-on-deinit-cleanup
>
> self.ptr = UnsafeMutablePointer<T>(allocatingCapacity: count)
> deferringDeInit { self.ptr.deallocateCapacity(count) }
>
> Or something.
>
> -- E
> p.s. Normally I put them on the same line with a semicolon but dang these things can be long
>
>> On Jun 8, 2016, at 10:54 AM, Graham Perks via swift-evolution <swift-evolution at swift.org <https://lists.swift.org/mailman/listinfo/swift-evolution> <mailto:swift-evolution at swift.org <https://lists.swift.org/mailman/listinfo/swift-evolution>>> wrote:
>>
>> Teach init a 'defer'-like ability to deinit
>>
>> 'defer' is a great way to ensure some clean up code is run; it's declaritive locality to the resource acquisition is a boon to clarity.
>>
>> Swift offers no support for resources acquired during 'init'.
>>
>> For an example, from https://www.mikeash.com/pyblog/friday-qa-2015-04-17-lets-build-swiftarray.html <https://www.mikeash.com/pyblog/friday-qa-2015-04-17-lets-build-swiftarray.html>
>>
>> init(count: Int = 0, ptr: UnsafeMutablePointer<T> = nil) {
>> self.count = count
>> self.space = count
>>
>> self.ptr = UnsafeMutablePointer<T>.alloc(count)
>> self.ptr.initializeFrom(ptr, count: count)
>> }
>>
>> deinit {
>> ptr.destroy(...)
>> ptr.dealloc(...)
>> }
>>
>> Another 'resource' might be adding an NSNotificationCenter observer, and wanting to unobserve in deinit (no need in OS X 10.11, iOS 9, but for earlier releases this is a valid example).
>>
>> Changing the above code to use a 'defer' style deinit block might look like:
>>
>> init(count: Int = 0, ptr: UnsafeMutablePointer<T> = nil) {
>> self.count = count
>> self.space = count
>>
>> self.ptr = UnsafeMutablePointer<T>.alloc(count)
>> self.ptr.initializeFrom(ptr, count: count)
>>
>> deinit {
>> ptr.destroy(...)
>> ptr.dealloc(...)
>> }
>>
>> // NSNotificationCenter example too
>> NSNotificationCenter.defaultCenter().addObserver(...)
>> deinit {
>> NSNotificationCenter.defaultCenter().removeObserver(...)
>> }
>> }
>>
>> The need to provide a separate implemention of deinit is gone. Reasoning for 'defer' applies here. There is good locality between what was initialized and what needs cleaning up.
>>
>> Considerations:
>> 1. Should deinit blocks be invoked before or after code in an explicit deinit method?
>> 2. Should deinit blocks be allowed in other methods; e.g. viewDidLoad()?
>> 3. How should deinit blocks be prevented from strongly capturing self (thus preventing themselves from ever running!)?
>


(Xiaodi Wu) #2

I really love this idea. My mental model of it is that it is exactly like
‘defer’, except it works on the lifetime of the object instance instead of
a function/method. Same thing, different scope.

I like how the creation and destruction are right next to one another. It
also solves a lot of potential issues with partial initialization, I
believe.

I might spell it ‘deferToDeinit’ or 'deferUntilDeinit'

The only issue I see is accidentally capturing self strongly. Is there a
way to mark a closure as implicitly unowned self so the end programmer
doesn’t have to worry about it?

I really like this idea as well. Does it need to be a regular closure? This
is one of those things that can be built into the language itself, surely?
Then the implicitly unowned self part would be taken care of...

···

On Fri, Jun 10, 2016 at 9:55 PM, Jonathan Hull via swift-evolution < swift-evolution@swift.org> wrote:

Thanks,
Jon

Twitter tl;dr:
>* Brent: So each instance must remember which init was used for it and then run the matching deinit code at deinit time?
*>* Me: In my version, the constructive act and destructive act are always paired, even redundantly, using a stack if needed
*>* Graham: so all your deferredDeinit blocks would run, no matter which init was invoked?
*>* Brent: Closure stack in the worst case. Might be able to optimize to something cheaper if no captures. Degenerate case: `for i in 0..<10 { deinit { print(i) }
*
So continuing on from Twitter, assuming the compiler cannot optimize in the case of multiple inits, and init-redirections, how about allowing traditional deinit as well, and introduce compile-time optimization into traditional de-init if the compiler finds only one initialization path per class? We can also warn anyone using my version in a complicated degenerate way that it can be costly through education, manual, etc. It would also help if (especially in Cocoa), you could legally use shared initialization setup closures.

If I create an observer, I want to be able to handle its end-of-life at that point. If I allocate memory, ditto. Etc etc. Surely Swift should be able to support doing this.

-- E

>* On Jun 8, 2016, at 3:43 PM, Erica Sadun via swift-evolution <swift-evolution at swift.org <https://lists.swift.org/mailman/listinfo/swift-evolution>> wrote:
*> >* I really like this idea. Spatially moving cleanup next to unsafe operations is good practice.
*> >* In normal code, I want my cleanup to follow as closely as possible to my unsafe act:
*> >* let buffer: UnsafeMutablePointer<CChar> = UnsafeMutablePointer(allocatingCapacity: chunkSize)
*>* defer { buffer.deallocateCapacity(chunkSize) }
*> >* (Sorry for the horrible example, but it's the best I could grep up with on a moment's notice)
*> >* I like your idea but what I want to see is not the deinit child closure in init you propose but a new keyword that means defer-on-deinit-cleanup
*> >* self.ptr = UnsafeMutablePointer<T>(allocatingCapacity: count)
*>* deferringDeInit { self.ptr.deallocateCapacity(count) }
*> >* Or something.
*> >* -- E
*>* p.s. Normally I put them on the same line with a semicolon but dang these things can be long
*> >>* On Jun 8, 2016, at 10:54 AM, Graham Perks via swift-evolution <swift-evolution at swift.org <https://lists.swift.org/mailman/listinfo/swift-evolution> <mailto:swift-evolution at swift.org <https://lists.swift.org/mailman/listinfo/swift-evolution>>> wrote:
*>> >>* Teach init a 'defer'-like ability to deinit
*>> >>* 'defer' is a great way to ensure some clean up code is run; it's declaritive locality to the resource acquisition is a boon to clarity.
*>> >>* Swift offers no support for resources acquired during 'init'.
*>> >>* For an example, from https://www.mikeash.com/pyblog/friday-qa-2015-04-17-lets-build-swiftarray.html <https://www.mikeash.com/pyblog/friday-qa-2015-04-17-lets-build-swiftarray.html>
*
>> >>* init(count: Int = 0, ptr: UnsafeMutablePointer<T> = nil) {
*>>* self.count = count
*>>* self.space = count
*>> >>* self.ptr = UnsafeMutablePointer<T>.alloc(count)
*>>* self.ptr.initializeFrom(ptr, count: count)
*>>* }
*>> >>* deinit {
*>>* ptr.destroy(...)
*>>* ptr.dealloc(...)
*>>* }
*>> >>* Another 'resource' might be adding an NSNotificationCenter observer, and wanting to unobserve in deinit (no need in OS X 10.11, iOS 9, but for earlier releases this is a valid example).
*>> >>* Changing the above code to use a 'defer' style deinit block might look like:
*>> >>* init(count: Int = 0, ptr: UnsafeMutablePointer<T> = nil) {
*>>* self.count = count
*>>* self.space = count
*>> >>* self.ptr = UnsafeMutablePointer<T>.alloc(count)
*>>* self.ptr.initializeFrom(ptr, count: count)
*>> >>* deinit {
*>>* ptr.destroy(...)
*>>* ptr.dealloc(...)
*>>* }
*>> >>* // NSNotificationCenter example too
*>>* NSNotificationCenter.defaultCenter().addObserver(...)
*>>* deinit {
*>>* NSNotificationCenter.defaultCenter().removeObserver(...)
*>>* }
*>>* }
*>> >>* The need to provide a separate implemention of deinit is gone. Reasoning for 'defer' applies here. There is good locality between what was initialized and what needs cleaning up.
*>> >>* Considerations:
*>>* 1. Should deinit blocks be invoked before or after code in an explicit deinit method?
*>>* 2. Should deinit blocks be allowed in other methods; e.g. viewDidLoad()?
*>>* 3. How should deinit blocks be prevented from strongly capturing self (thus preventing themselves from ever running!)?
*>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Charlie Monroe) #3

I really love this idea. My mental model of it is that it is exactly like ‘defer’, except it works on the lifetime of the object instance instead of a function/method. Same thing, different scope.

I like how the creation and destruction are right next to one another. It also solves a lot of potential issues with partial initialization, I believe.

I might spell it ‘deferToDeinit’ or 'deferUntilDeinit'

The only issue I see is accidentally capturing self strongly. Is there a way to mark a closure as implicitly unowned self so the end programmer doesn’t have to worry about it?

I really like this idea as well. Does it need to be a regular closure? This is one of those things that can be built into the language itself, surely? Then the implicitly unowned self part would be taken care of...

This should be handled by the compiler and the code within the block should be used to create a deinit method.

Several issues to deal with:

- if there are deferred de-inits in init, is regular deinit {} allowed? If yes, does the deferred deinit code from init get called before or after?

- what if there are several initializers and each has its own deferred de-inits? Would the instance need to keep the information which initializer was used and call proper deinit based on that?

···

On Jun 11, 2016, at 5:18 AM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:
On Fri, Jun 10, 2016 at 9:55 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Thanks,
Jon

Twitter tl;dr:
> Brent: So each instance must remember which init was used for it and then run the matching deinit code at deinit time?
> Me: In my version, the constructive act and destructive act are always paired, even redundantly, using a stack if needed
> Graham: so all your deferredDeinit blocks would run, no matter which init was invoked?
> Brent: Closure stack in the worst case. Might be able to optimize to something cheaper if no captures. Degenerate case: `for i in 0..<10 { deinit { print(i) }

So continuing on from Twitter, assuming the compiler cannot optimize in the case of multiple inits, and init-redirections, how about allowing traditional deinit as well, and introduce compile-time optimization into traditional de-init if the compiler finds only one initialization path per class? We can also warn anyone using my version in a complicated degenerate way that it can be costly through education, manual, etc. It would also help if (especially in Cocoa), you could legally use shared initialization setup closures.

If I create an observer, I want to be able to handle its end-of-life at that point. If I allocate memory, ditto. Etc etc. Surely Swift should be able to support doing this.

-- E

> On Jun 8, 2016, at 3:43 PM, Erica Sadun via swift-evolution <swift-evolution at swift.org <https://lists.swift.org/mailman/listinfo/swift-evolution>> wrote:
>
> I really like this idea. Spatially moving cleanup next to unsafe operations is good practice.
>
> In normal code, I want my cleanup to follow as closely as possible to my unsafe act:
>
> let buffer: UnsafeMutablePointer<CChar> = UnsafeMutablePointer(allocatingCapacity: chunkSize)
> defer { buffer.deallocateCapacity(chunkSize) }
>
> (Sorry for the horrible example, but it's the best I could grep up with on a moment's notice)
>
> I like your idea but what I want to see is not the deinit child closure in init you propose but a new keyword that means defer-on-deinit-cleanup
>
> self.ptr = UnsafeMutablePointer<T>(allocatingCapacity: count)
> deferringDeInit { self.ptr.deallocateCapacity(count) }
>
> Or something.
>
> -- E
> p.s. Normally I put them on the same line with a semicolon but dang these things can be long
>
>> On Jun 8, 2016, at 10:54 AM, Graham Perks via swift-evolution <swift-evolution at swift.org <https://lists.swift.org/mailman/listinfo/swift-evolution> <mailto:swift-evolution at swift.org <https://lists.swift.org/mailman/listinfo/swift-evolution>>> wrote:
>>
>> Teach init a 'defer'-like ability to deinit
>>
>> 'defer' is a great way to ensure some clean up code is run; it's declaritive locality to the resource acquisition is a boon to clarity.
>>
>> Swift offers no support for resources acquired during 'init'.
>>
>> For an example, from https://www.mikeash.com/pyblog/friday-qa-2015-04-17-lets-build-swiftarray.html <https://www.mikeash.com/pyblog/friday-qa-2015-04-17-lets-build-swiftarray.html>
>>
>> init(count: Int = 0, ptr: UnsafeMutablePointer<T> = nil) {
>> self.count = count
>> self.space = count
>>
>> self.ptr = UnsafeMutablePointer<T>.alloc(count)
>> self.ptr.initializeFrom(ptr, count: count)
>> }
>>
>> deinit {
>> ptr.destroy(...)
>> ptr.dealloc(...)
>> }
>>
>> Another 'resource' might be adding an NSNotificationCenter observer, and wanting to unobserve in deinit (no need in OS X 10.11, iOS 9, but for earlier releases this is a valid example).
>>
>> Changing the above code to use a 'defer' style deinit block might look like:
>>
>> init(count: Int = 0, ptr: UnsafeMutablePointer<T> = nil) {
>> self.count = count
>> self.space = count
>>
>> self.ptr = UnsafeMutablePointer<T>.alloc(count)
>> self.ptr.initializeFrom(ptr, count: count)
>>
>> deinit {
>> ptr.destroy(...)
>> ptr.dealloc(...)
>> }
>>
>> // NSNotificationCenter example too
>> NSNotificationCenter.defaultCenter().addObserver(...)
>> deinit {
>> NSNotificationCenter.defaultCenter().removeObserver(...)
>> }
>> }
>>
>> The need to provide a separate implemention of deinit is gone. Reasoning for 'defer' applies here. There is good locality between what was initialized and what needs cleaning up.
>>
>> Considerations:
>> 1. Should deinit blocks be invoked before or after code in an explicit deinit method?
>> 2. Should deinit blocks be allowed in other methods; e.g. viewDidLoad()?
>> 3. How should deinit blocks be prevented from strongly capturing self (thus preventing themselves from ever running!)?
>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution