Should Swift apply "statement scope" for ARC

I haven't used it myself, but is this the use case addressed by `withExtendedLifetime(_:_:)`?

Yeah, if you want to vend resources managed by an object to consumers outside of that object like this, you need to use withExtendedLifetime to keep the object alive for as long as you're using the resources. A cleaner way to model this might be to put the class or protocol in control of handling the I/O to the file handle, instead of vending the file handle itself, so that the ownership semantics fall out more naturally:

protocol Storage {
  func write(bytes: UnsafeRawPointer, count: Int)
}

func save(string: String, to: Storage?) {
    if let data = string.data(using: String.Encoding.utf8) {
        data.withUnsafeBytes {
            _ = to?.write(bytes: $0, count: data.count)
        }
    }
}

-Joe

···

On Sep 21, 2016, at 3:14 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

On Wed, Sep 21, 2016 at 16:54 John Holdsworth via swift-evolution <swift-evolution@swift.org> wrote:
Hi,

For complex statements in C++ any temporary instances created in the course
of an expression have their lifetime extended to the completion of the current
statement after which they are all deallocated en masse. This makes certain
types of language usage possible and easier to reason with.

I’m bringing this up as I had a problem with some code crashing only when
compiled with release configuration and the problem could have been avoided
if Swift deferred deallocation to the end of a statement. While Swift’s ARC policy
is consistent in itself this seems to be a particular problem interfacing between
language/reference counting systems. My problem code was a Java-Swift Bridge.

A contrived example:

import Foundation

protocol Storage {
    var fp: UnsafeMutablePointer<FILE> { get }
}

class FileStorage: Storage {

    let fp: UnsafeMutablePointer<FILE>

    init?(path: String, mode: String = "w") {
        print("Opening")
        let fp = fopen(path, mode)
        if fp == nil {
            return nil
        }
        self.fp = fp!
    }

    deinit {
        print("Closing")
        fclose(fp)
    }
}

func save(string: String, to: Storage?) {
    if let data = string.data(using: String.Encoding.utf8) {
        print("Saving1")
        if let fp = to?.fp {
            print("Saving2")
            data.withUnsafeBytes {
                _ = fwrite($0, 1, data.count, fp)
            }
            print("Saving3")
        }
    }
}

save(string: "Hello World\n", to: FileStorage(path: "/tmp/a.txt"))

In debug configuration is prints:
Opening
Saving1
Saving2
Saving3
Closing

Whereas in release configuration it prints:
Opening
Saving1
Closing <!!!
Saving2
Saving3

The optimiser is vigorously deallocating objects when they are no longer referenced regardless
of whether an variable referencing it is still in scope (In fairness this particular problem only occurs
for Optional augments of Protocols) but this behaviour seems to be implicit in the current language
spec. The alternative is to retain arguments themselves as I believe they are in Objective-C ARC.

This would have been avoided if the temporary FileStorage instance has been considered to have
a lifetime up to the end of the statement calling function save() and hence the duration of the call.
This needed increase ARC overhead in any way. Just alter the timing of it to be more conservative.

John

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