Implicitly calling a destructor C function in modulemap-generated interfaces?

Another team at my company develops this C API that we need to use from Swift. It more-or-less follows the pattern:

Foo* f = FooCreate();
FooDoSomething(f, …);
FooDestroy(f);

We can add a modulemap file to the build so that Swift can see a nicer API, but it got me thinking, is f managed by ARC, and how do I get it to call FooDestroy() implicitly?

You have to wrap it into a Swift object that has its deinit call your Destroy function.

Bah. I was trying to avoid that. Seems like something we could tell the compiler to do implicitly?

The issue is in Swift that you have an UnsafePointer<Foo> which as a struct has no destructor you can customize. You could use a property wrapper if you are targeting Swift 5.1. It would be a class rather than struct and then all the usage just continues to be with it as a pointer.

A very quick and dirty property wrapper that does this would be this:

@propertyWrapper
class OnDeinit<Value> {
    private let onDeinit: (Value) -> Void
    public var wrappedValue: Value

    init(wrappedValue: Value, execute onDeinit: @escaping (Value) -> Void) {
        self.wrappedValue = wrappedValue
        self.onDeinit = onDeinit
    }

    deinit {
        onDeinit(wrappedValue)
    }
}

I tested it with this in a playground:

struct Foo {
    @OnDeinit(execute: { i in
        print(i)
    }) var i = 0
}

var f = UnsafeMutablePointer<Foo>.allocate(capacity: 1)
f.initialize(to: Foo())
f.pointee.i = 42
f.deinitialize(count: 1)
f.deallocate()

So your usage would should be this with OnDeinit:

@OnDeinit(execute: FooDestory) var foo = FooCreate()

You might also want to change the setter of wrappedProperty to call the onDeinit function before reassignment of foo but that will depend on your type and use case. This could even be a flag on the OnDeinit type.

This strikes me as very inelegant. It does need to be a class, not a struct. I can target Swift 5.1, but I don't know how I'd use the new property wrappers to manage this object.

Well once you drop into C you have to manage memory yourself so there aren't any elegant solutions. In Swift you have to choose either manage memory manually with the UnsafePointer APIs or put it in a class that acts as a box where the deinit ensures you clean up memory.

Either way you have to tie the memory management together. Foundation types backed by Core Foundation takes care of calling CFRetain() and CFRelease() for you during their init/deinit and any other appropriate times.

You could write a custom class that acts as a shim around a Foo* and that would give you the best and swiftiest interface when using it, that is what I normally do. The property wrapper OnDeinit that I posted is very simple (I spent about 1 minute throwing it together in a playground) but it gets the job done if you just need a very simple solution and it isn't going to be used all over the place.

So, how does CGContext work (for example)? It's a C API that must be retained and released, and as you say Foundation handles this for you, but how can I make a C API behave similarly?

CGContext is imported as an @objc class because CGContextRef is annotated as __attribute__((objc_bridge(id))). The easiest way to get the same behaviour for your type would be, well, to implement it in Objective-C.

One could imagine an __attribute__((swift_import_as_class)) that can be applied to C pointer types, along with __attribute__((swift_name(Foo.deinit))) on FooDestroy, but I don’t think anything like that exists now.

What's missing here to behave as a class is retain and release so it works with reference counting. If you only have a deinit, then it should behave as a move-only pointer. The language has no support for move-only types (for now).

I was imagining a class wrapping the C type, but I’m sure all sorts of fun surprises would come up in practice.

And I was imagining something more similar to CF types, only with customized functions to call to manage the lifetime.

Anyway, with today's Swift, writing a class wrapper is the only option to have the language manage the lifetime. But it might or might not be worth it depending on the usage.

CoreGraphics is closed-source, but I would guess it has an Obj-C compatible header like the CoreFoundation “objects”. This is a private Apple feature which relies on Obj-C runtime details.

Really, since this thing has an independent lifetime, a class is the correct solution. This is also what stdlib containers do.

If you want value semantics, you can wrap the class in a struct and implement copy-on-write.

No need to complicate things.

CoreGraphics uses the CF object system, which is ObjC-compatible.

We'd like Swift to someday be able to import "foreign" class types that use their own reference-counting mechanism, but that's lower priority than a lot of other work.

In the original example, Foo is a unique-ownership type; this is a common idiom that we'd also like to be able to import, but doing so will require adding language support for move-only types. See the Ownership Manifesto for more information.

3 Likes
Terms of Service

Privacy Policy

Cookie Policy