Prevent ARC from releasing a ref type inside a value type

Hello! I have a buffer written in C that can take Swift structs, which sometimes will contain references.

I need to figure out a way to increase the ref count on the references of those structs whenever they go into my C buffer.

Something like this:

public class Deinit
{
    deinit
    {
        // This shouldn't run.
        print("It deinit!");
    }
}

public struct Test
{
    let theDeinit = Deinit();
}

main()
{
        var doTest = Test();
        var buf = try! Buffer<Test>();
        try! buf.Add(doTest);
        // I don't want ARC to release the reference to theDeinit here because buf now has a copy of doTest. 

}

Thank you!

We'll need to see how you've written your C type to be helpful here: you clearly have an abstraction type of some kind here (because Buffer is a generic type, which C cannot express), so there are some moving pieces here that are important to giving you a correct answer.

Buffer is essentially just a wrapper around a malloc'd pointer of x size. buf.Add just takes the struct and memcpy's it into the buffer, nothing fancy. :)

OK, so the short answer is that you cannot copy an arbitrary Swift structure with memcpy. Swift types effectively have "copy constructors" (to steal terminology from C++). While many Swift types are "trivial", and so have copy constructors that are equivalent to memcpy, not all of them are, and this is a good example of one such case.

The correct idiom to handle this is to "bind" the memory inside Buffer. That is, you should take the UnsafeMutableRawPointer returned from malloc and call bindMemory on it, telling it that it stores Test objects. You can then assign the types into the pointer by using .initialize, and remove them using .deinitialize.

1 Like

That makes sense! I unfortunately have to use my C buffer (I'm writing a game engine), so I guess I'll just have to go digging around the Swift source code to see how UnsafeMutableRawPointer internally does it!

Thanks for the help! I'll post my findings here once I've figured it out. :)

I don't see why the fact that the buffer is written in C would matter. You can bind the memory in your Swift abstraction.

1 Like

Right, I see what you're suggesting. That could certainly be an option.

That's a nice solution to the problem, thanks a lot! :)

Was just coming back here to note that I found a more sleek way to achieve what I was looking to do through a bit of runtime hacking, in case this is useful to anyone:

public func swift_copyValue(dest: Ptr<VoidPtr>, source: Ptr<VoidPtr>, type: Any.Type)
{
    let typeMetadata = try! metadata(of: type);
    
    switch typeMetadata.kind
    {
    
    case .struct:
    let structMeta: StructMetadata = StructMetadata(type: type);
    _ = structMeta.valueWitnessTable.pointee.assignWithCopy(dest.raw!, source.raw!, structMeta.pointer);

    default:
        return;
    }
}

The above function takes in a pointer to the destination for your value type, and a pointer to the source of your value type, as well as the type. I'm using a slightly modified version of Wes Wickwire's Runtime to use the value witness table to tell Swift to do a copy into my buffer at the right place, just like the compiler would be doing behind the scenes. You can grab the modified version in the repo for the game engine I'm writing.