How can I allocate a memory for a Swift struct from C++?

Let's think how to swiftify the following C++ class Foo with c++ interoperability feature.

class Foo {
    Foo() { ... }
    void doAdditionalConfiguration() { ... }
};

// A function to allocate memory in the heap area and returns the raw pointer
Foo* setupFoo() {
    Foo* fooPtr = new Foo();
    fooPtr -> doAdditionalConfiguration();
    return fooPtr;
}

Foo will be like the following with Swift.

// In "FooModule"
@_expose(Cxx) public struct Foo {
    public init() { ... }
    public func doAdditionalConfiguration() { ... }
}

In this case, how can we update setupFoo() c++ function? The initializer of the swiftified Foo is FooModule::Foo::init, and the c++ constructor Foo() is disabled. So, we cannot use new FooModule::Foo() syntax to allocate the memory.

Once I came up with the idea of the following code, but it cannot be compiled.

Foo* setupFoo() {
    Foo* fooPtr = (Foo*)malloc(sizeof(Foo));
    *fooPtr = FooModule::Foo:init(); // object of type 'FooModule::Foo' cannot be assigned because its copy assignment operator is implicitly deleted
    fooPtr -> doAdditionalConfiguration();
    return fooPtr;
}

How can I write setupFoo function?

There's no good way to do this because at the moment we can't move the Swift values in C++. Once we have move support in the future, you should be able to do:

new Foo(FooModule::Foo::init());

Unlike malloc, calloc returns memory initialized to zeroes. If "all zeroes" is valid for the struct (which it won't always be -- see below), you could do something like this:

@_expose(Cxx) public struct Foo {
    // let's say this just holds some integers
    var bar: Int
    var baz: Int
    // and that the default init does this:
    public init() {
        self.bar = 0
        self.baz = 1
    }
    // Then, C++ code can call this:
    public mutating func initializeAssumingZeroed() {
        self.baz = 1
    }
}
Foo* setupFoo() {
    Foo* fooPtr = (Foo*)calloc(1, sizeof(Foo));
    fooPtr->initializeAssumingZeroed();
    return fooPtr;
}

There are some cases in which "all zeroes" isn't valid. Off the top of my head, this will happen in at least the following scenarios:

  • the struct contains a non-nullable reference, e.g. NSObject, String, or UnsafePointer
  • the struct contains an enum with associated values -- this is valid sometimes, but I'm not sure when

This kind of approach should not be used. It violates the language semantics of both C++ and Swift because both the Swift value and the C++ isn't constructed correctly, so the C++ compiler is free to remove this code as it's a source of undefined behavior. Furthermore, structures that don't have a fixed layout and/or non-trivial structures might end up either having additional fields in C++ that require correct initialization in order to be passed to Swift correctly and/or might actually be allocated on the heap if their layout is not known at compile time.

Ah, I wasn’t aware of that. As much as I’ve used Swift and C++ independently, I don’t have much experience with using Swift types in C++.

Hm, would something like this work then?

@_expose(Cxx) public struct Foo {
    public init() { ... }
    public static func allocate() -> UnsafeMutablePointer<Foo> {
        let ptr = UnsafeMutablePointer<Foo>.allocate(capacity: 1)
        ptr.initialize(to: Foo())
        return ptr
    }
    public static func deallocate(_ ptr: UnsafeMutablePointer<Foo>) {
        ptr.deinitialize(count: 1).deallocate()
    }
}
Foo* setupFoo() {
    Foo* fooPtr = Foo::allocate();
    fooPtr->doAdditionalConfiguration();
    return fooPtr;
}

Actually I tried it, and surprisingly, allocate was not exported to the module header and not visible from C++.
And, I wonder why a swift struct is not copyable on C++. Since it is a value type and the instance is not managed by ARC or some other memory management systems, it looks like there is not problem to simply copy the struct.

1 Like

If any of its fields are, it can't be copied byte-for-byte without retaining that field. A "trivial" struct, like @Alex_L mentioned above, can only have integers, booleans, C structs (but not necessarily other Swift structs), and certain enums. Even Swift's value-semantic collections like String, Set, and Array aren't trivial.

I believe static methods are currently not supported. Here's the markdown file documenting current C++ interop status: https://github.com/apple/swift/blob/main/docs/CppInteroperability/CppInteroperabilityStatus.md

Interestingly, this static function was properly exported to the module header.

@_expose(Cxx) public struct Foo {
    public static func incrementPointee(pointer: UnsafeMutablePointer<UInt32>) {
        pointer.pointee += 1
    }
}

Yeah you're right, the document may be outdated. Probably it's UnsafeMutablePointer<Foo> not supported so that the static function is not printed.