

Once this work is done, you can forget everything about manual memory management because the system will always do the right thing for you. This is very nice because the class definition cleanly defines and encapsulates the point of contact between the two different memory management strategies. This Swift code wraps the C type into a class where we override the destructor and call freeFoo whenever Swift decides it's a good time to free the object. This is not how you would normally want to do that, since the struct could be passed by value with no downsides, but let's roll with it for illustrative purposes. In this Zig code we allocate on the heap a new instance of ZigFoo every time createFoo is invoked. All you'll have to do is expose a function to free memory from Zig to Swift and call it in the class destructor. Swift makes this very easy by using classes. In simple cases, the lifetime of such memory is directly tied to the lifetime of the corresponding Swift object: once it stops being referenced anywhere and it's about to get garbage collected, that's when we'll want to free the relative memory allocated by Zig. When calling a Zig function that returns a pointer to dynamically allocated memory, you will be in charge of freeing such memory at the right moment. Let's see what to do if the bytes are allocated on the heap instead.

In our previous article we sent a string pointer to Swift, but we didn't need to do anything else because the referenced bytes were part of the data section of our Zig library. Pointers are also special because, while the pointer itself is a value type handled directly by Swift, the memory that it references might need to be managed. You can learn more about their constructors and methods in the official Swift docs. Enter fullscreen mode Exit fullscreen mode
