Search code examples
swifttype-erasure

Type erasure: do we risk non-reversibly losing access to kept-alive data of the instance of the erased type, when erasing the type information?


Consider the following common simple type erasure scheme

protocol Foo {
    associatedtype Bar
    func bar() -> Bar
}

struct AnyFoo<Bar>: Foo {
    private let _bar: () -> Bar

    init<F: Foo>(_ foo: F) where F.Bar == Bar {
        _bar = foo.bar
            /* stores a reference to foo.bar,
               so foo kept alive by ARC? */
    }

    func bar() -> Bar {
        return _bar()
    }
}

Assume the initializer argument foo above is (intended to be) a temporary instance of a "large" type, from which we're only interested to slice out the information blueprinted by Foo (i.e., the bar() method).

struct Huge { /* ... */ }

struct Foobar: Foo {
    internal func bar() -> String {
        return "foo"
    }
    let lotsOfData: Huge = Huge()
}

func getAnyFooStr() -> AnyFoo<String> {
    let foobar = Foobar()
    return AnyFoo(foobar)
}

let anyStrFoo = getAnyFooStr()
    /* we can now access anyStrFoo.bar() (-> "foo") from our
       erased type, but do lotsOfData of the underlying seemingly
       temporary Foobar() instance still "live", unreachable? */
  • Q: Will we still keep the remaining content of foo alive and unreachable in memory due to the fact that closures are reference types? And if so, I assume we would never be able to reclaim access to this lost-but-alive content?

(I tried the above with Foobar as a class, monitoring the (lack of a) deinit call, but I've confused myself so much the last hour that I need some non-self verification for this, especially for the case when Foobar is a value type)


Xcode 8.0 / Swift 3.


Solution

  • Yes, and it has nothing to do with type erasure :) The closure Foobar().bar keeps the instance that it is bound to (with all its properties) alive as long as the closure is alive.

    Here is a simplified example:

    class Huge {
        deinit { print("deinit Huge") }
    }
    
    struct Foobar {
        internal func bar() -> String {
            return "foo"
        }
        let lotsOfData: Huge = Huge()
    }
    
    do {
        let fb  = Foobar().bar // the type of `fb` is `() -> String`
        print("still alive ...")
    }
    print("... out of scope now")
    

    Output:

    still alive ...
    deinit Huge
    ... out of scope now