Is there a way to declare/import C type as a noncopyable struct in Swift

Currently we can use typedef + __attribute((swift_newtype(struct))) to import a C struct as a Swift struct.

eg.

typedef struct DemoData_s {
...
} DemoData;
typedef DemoData *Demo __attribute((swift_newtype(struct)));
typedef uint32_t Demo2 __attribute((swift_newtype(struct)));

:arrow_down:

// And they will be imported as the following Swift interface
struct Demo { ... }
struct Demo2 { let rawValue: UInt32 }

I was wondering is there any compiler attribute that can mark a C API into a noncopyable struct in Swift.

struct A: ~Copyable { ... }

You could encapsulate the type in a Swift overlay: annotate the C type as swift_private (NS_REFINED_FOR_SWIFT) at its declaration, then on the Swift side write a wrapper type that's non-copyable:

struct DemoData: ~Copyable {
  private var cDemoData: __DemoData_s

  init(unsafeCDemoData cDemoData: consuming __DemoData_s) {
    self.cDemoData = cDemoData
  }

  deinit {
    __demo_data_destroy(cDemoData)
  }

  borrowing func runDemo() {
    __demo_data_run(cDemoData)
  }
}

Where __demo_data_destroy() and __demo_data_run() are hypothetical interfaces from C that operate on the C type and which have also been marked swift_private.

2 Likes

Got it. Although I prefer to annotate directly in C header via NS_SWIFT_NAME instead of adding a Swift overlay. This is indeed a more flexible way to solve my problem currently.

eg.

// Demo.h
typedef DemoData *Demo __attribute((swift_newtype(struct)));

NS_EXPORTED
REFINED_FOR_SWIFT
DemoData DemoCreate() NS_SWIFT_NAME(Demo.init()); 

NS_EXPORTED
REFINED_FOR_SWIFT
void DemoGetValue() NS_SWIFT_NAME(getter:Demo.value(self:));

Applying your solution to add an overlay, it would become the following code I guess.

// Demo.h
typedef DemoData *_Demo __attribute((swift_newtype(struct)));

NS_EXPORTED
REFINED_FOR_SWIFT
DemoData DemoCreate(); 

NS_EXPORTED
REFINED_FOR_SWIFT
int DemoGetValue();
struct Demo: ~Copyable {
  private var cDemo: _Demo
  init() { cDemo = __DemoCreate() }
  deinit { __DemoDestory(self) }
  var value: Int { __DemoGetValue(self) }
}

You can apply NS_REFINED_FOR_SWIFT to the type declaration too, so that in C it is named Demo and code can directly use it, while in Swift it is renamed __Demo and code works with the Swift wrapper instead (which appears, to calling code, to be exactly the same except Swiftier.)

2 Likes

Thanks for the nice catch up. :+1: