# \[Pitch\] Import C structs with ARC pointer members

**URL:** <https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059>\
**Category:** Pitches\
**Tags:** c-interop, objc, cxx-interop, sil, clangimporter\
**Created:** [March 29, 2023, 6:14pm UTC](https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059 "2023-03-29T18:14:19Z")\
**Posts on this page:** 16\
**Page:** 1

<div class="post-metadata">

**Author:** ![AdamCmiel](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/adamcmiel/32/15453_2.png) [@AdamCmiel](https://forums.swift.org/u/AdamCmiel)\
**Post date:** [March 29, 2023, 6:14pm UTC](https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059/1 "2023-03-29T18:14:19Z")

</div>

Happy Wednesday everyone! We’d like to propose an extension to the ClangImporter for Swift \<\> Objective-C Interop.

See a draft PR for importing Automatic Reference Counting (ARC) pointers in C structs in Objective-C mode here: [ClangImporter: Allow for structs with arc pointers to be constructed in objc mode by AdamCmiel · Pull Request #64381 · swiftlang/swift · GitHub](https://github.com/apple/swift/pull/64381/files). Note that the draft does not do type bridging for strong references yet, as that involves more SILgen work and I'd like some feedback before venturing down that route.

## Introduction:

Today Swift can import C structs whose members are Swift-importable value types such as int, BOOL, and \_\_unsafe\_unretained ARC pointers, which are all trivially construct- and destructable. Building on [Akira’s](https://reviews.llvm.org/p/ahatanak/) work enabling [strong](https://reviews.llvm.org/D41228) and [weak references](https://reviews.llvm.org/rGc43ecf87c82e29b35b6e0deaa9492b1cb5725abe) in LLVM, we should be able to import these types and declarations.

## Motivation:

Since 2018, these pointer types have been available to use in Objective-C/Objective-C++ (and before that in Objective-C++) and Swift has, to date, not been extended to import them. This is a necessary part of Swift \<\> C++ interop, and we should be able to port default and member-wise construction and destruction of reference type members of value types, including ARC pointers in structs in Objective-C language mode. Puyan has already merged some code for constructing these types using C++ interop [here](https://github.com/apple/swift/commit/4f2f3483562207a140153c16df32f066162c6785).

## Proposed Solution:

```swift
#import <Foundation/Foundation.h>

typedef struct WithArcPointers {
    __unsafe_unretained NSString *usUR;
    __strong _Nonnull NSString *sStr;
    __weak _Nullable NSString *wStr;
    NSInteger count;
} WithArcPointers;

```

should import to Swift as

```swift
struct WithArcPointers {
  init(usUR: String!, sStr: String, wStr: String?, count: Int)
  var usUR: Unmanaged<NSString>!
  var sStr: String
  weak var wStr: NSString?
  var count: Int
}

```

The various arc storage types should be preserved, with the expected type bridging and nullability annotations respected where possible. Weak members must be Optional on the Swift side, and \_\_unsafe\_unretained as Unmanaged of an unbridged type. These structs should be member-wise constructable, and if they contain no nonnull members, trivially constructable as well with an available init().

Nonnull weak references in structs, while legal in Clang, will not be imported and the compiler will emit an appropriate diagnostic, as Swift requires weak references to be Optional. The C declaration should probably have a diagnostic as well, in line with ObjC classes that appropriately error, but that’s out of scope of this proposal.

## Source Compatibility

This change may introduce name conflicts for users that have created accessible declarations with the same name as any newly imported Clang declarations. Thus we suggest keeping the change off by default initially, and possibly enabling by default for Swift 6.0

## Alternatives Considered

Because rewriting those C structs types as Objective-C types will involve non-trivial amount of metadata and more code generation (effectively turning each member access into a computed var with a getter and a setter), such a rewrite may be less desirable than importing the types unbridged. There are binary size tradeoffs to consider here as well as ergonomics of using the bridged Swift types.

More codegen for type bridging should happen in the SIL, but I haven’t gotten to that part yet. If there’s a better place to implement that (either in the ClangImporter or IRgen or all three), I’d appreciate the feedback.

## Feedback

We are looking for input on if the proposal can be implemented as described, and any possible edge cases we might not be thinking of. In particular, any member of the community that works with Objective-C interop, or any people working with the Objective-C++ Interop would be great!

---

<div class="post-metadata">

**Author:** ![tgoyne](https://avatars.discourse-cdn.com/v4/letter/t/5e9695/32.png) [@tgoyne](https://forums.swift.org/u/tgoyne)\
**Post date:** [March 29, 2023, 6:47pm UTC](https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059/2 "2023-03-29T18:47:10Z")

</div>

This is certainly something we (Realm) would like to have. It hasn't been a _big_ pain point, but we currently have a handful of internal obj-c wrappers for structs that exist purely to work around the lack of this, which is both some boilerplate and is bad for compiled library size.

If this is off by default then it would be a requirement for us that enabling it pre-Swift 6 be doable in a SPM-compatible way.

---

<div class="post-metadata">

**Author:** ![AdamCmiel](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/adamcmiel/32/15453_2.png) [@AdamCmiel](https://forums.swift.org/u/AdamCmiel)\
**Post date:** [March 29, 2023, 6:51pm UTC](https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059/3 "2023-03-29T18:51:55Z")

</div>

> It hasn't been a big pain point, but we currently have a handful of internal obj-c wrappers for structs that exist purely to work around the lack of this, which is both some boilerplate and is bad for compiled library size.

This is our primary motivation at Meta as well, enabling direct interop instead of (adding) massive numbers of ObjC classes.

---

<div class="post-metadata">

**Author:** ![jayton](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jayton/32/5592_2.png) [@jayton](https://forums.swift.org/u/jayton)\
**Post date:** [March 30, 2023, 11:19am UTC](https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059/4 "2023-03-30T11:19:36Z")

</div>

> [@AdamCmiel](#):
>
> Thus we suggest keeping the change off by default initially, and possibly enabling by default for Swift 6.0

What is the opposite of “by default”? Would this be enabled in Swift 5 only by a compiler flag, or could it be enabled in the ObjC code with an attribute?

---

<div class="post-metadata">

**Author:** ![stuchlej](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/stuchlej/32/7751_2.png) [@stuchlej](https://forums.swift.org/u/stuchlej)\
**Post date:** [March 30, 2023, 12:36pm UTC](https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059/5 "2023-03-30T12:36:30Z")

</div>

> [@AdamCmiel](#):
>
> ```swift
> struct WithArcPointers {
> init(usUR: String!, sStr: String, wStr: String?, count: Int)
> var usUR: Unmanaged<NSString>!
> var sStr: String
> weak var wStr: NSString?
> var count: Int
> }
> 
> ```

I struggle with this a little bit. How will the `init` behave in the 3rd case? The `init` will instantiate an instance of `NSString`, storing it in a `weak` pointer - and then immediately `release` the only strong reference in the _epilogue_?

---

<div class="post-metadata">

**Author:** ![drodriguez](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/drodriguez/32/4719_2.png) [@drodriguez](https://forums.swift.org/u/drodriguez)\
**Post date:** [March 31, 2023, 4:32pm UTC](https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059/6 "2023-03-31T16:32:02Z")

</div>

Correct. Probably an "upcoming feature" compiler flag: [swift/Features.def at 7064e18c0435648df17e01fdef77651650c6d1d9 · apple/swift · GitHub](https://github.com/apple/swift/blob/7064e18c0435648df17e01fdef77651650c6d1d9/include/swift/Basic/Features.def#L104-L108)

---

<div class="post-metadata">

**Author:** ![AdamCmiel](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/adamcmiel/32/15453_2.png) [@AdamCmiel](https://forums.swift.org/u/AdamCmiel)\
**Post date:** [May 5, 2023, 6:03pm UTC](https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059/7 "2023-05-05T18:03:05Z")

</div>

@stuchlej , good point, the weak and unsafe\_unretained params for this struct should both be NSString. I imagine for a possibly bridged type that the strong reference (as it is the default) would be by far the most common.

What I'm more looking for in this pitch is if the interface for the strong member _should_ be bridged, or if we should just expose the un-bridged type. That would significantly simplify the implementation, but may be awkward at the API.

My gut feeling is that they should in fact be bridged, but I need to dig more into the SILGen part of the implementation then.

@jayton , yep, as @drodriguez said, this availability would be behind a feature flag. I'll add that on the PR as well.

---

<div class="post-metadata">

**Author:** ![jayton](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jayton/32/5592_2.png) [@jayton](https://forums.swift.org/u/jayton)\
**Post date:** [May 6, 2023, 11:36am UTC](https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059/8 "2023-05-06T11:36:50Z")

</div>

> [@AdamCmiel](#):
>
> @jayton , yep, as @drodriguez said, this availability would be behind a feature flag. I'll add that on the PR as well.

Feature request: make it possible to opt-in on the C/ObjC side using an ` __attribute__ (( __swift_attr__ (importable_struct)))` or similar.

---

<div class="post-metadata">

**Author:** ![drodriguez](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/drodriguez/32/4719_2.png) [@drodriguez](https://forums.swift.org/u/drodriguez)\
**Post date:** [May 9, 2023, 4:26pm UTC](https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059/9 "2023-05-09T16:26:24Z")

</div>

Is there another example in the Clang Importer that uses that pattern?

Does your proposal to have the feature flag work over all the structs and have that attribute work when the feature flag is not enabled?

---

<div class="post-metadata">

**Author:** ![AdamCmiel](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/adamcmiel/32/15453_2.png) [@AdamCmiel](https://forums.swift.org/u/AdamCmiel)\
**Post date:** [May 9, 2023, 5:47pm UTC](https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059/10 "2023-05-09T17:47:45Z")

</div>

> Feature request: make it possible to opt-in on the C/ObjC side using an ` __attribute__ (( __swift_attr__ (importable_struct)))` or similar.

I'd rather opt-in with the feature flag and then if a given struct would like to opt-out of Swift visibility, could use the existing ` __attribute__ ((availability(swift, unavailable, message)))` with the convenient `NS_SWIFT_UNAVAILABLE` if linking Foundation

---

<div class="post-metadata">

**Author:** ![AdamCmiel](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/adamcmiel/32/15453_2.png) [@AdamCmiel](https://forums.swift.org/u/AdamCmiel)\
**Post date:** [June 17, 2026, 7:25pm UTC](https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059/11 "2026-06-17T19:25:10Z")

</div>

PR updated with a more complete implementation here: [ClangImporter: Allow for structs with arc pointers to be constructed in objc mode by AdamCmiel · Pull Request #64381 · swiftlang/swift · GitHub](https://github.com/swiftlang/swift/pull/64381)

---

<div class="post-metadata">

**Author:** ![John\_McCall](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/john_mccall/32/1351_2.png) [@John\_McCall](https://forums.swift.org/u/John_McCall)\
**Post date:** [October 5, 2026, 8:50pm UTC](https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059/12 "2026-10-05T20:50:32Z")

</div>

I see that there's a [proposal PR](https://github.com/swiftlang/swift-evolution/pull/3518) now, so I'll bump this thread by asking if there were any interesting restrictions or corner cases you ran into while implementing this.

---

<div class="post-metadata">

**Author:** ![AdamCmiel](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/adamcmiel/32/15453_2.png) [@AdamCmiel](https://forums.swift.org/u/AdamCmiel)\
**Post date:** [October 8, 2026, 4:59pm UTC](https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059/13 "2026-10-08T16:59:15Z")

</div>

> [@John\_McCall](#):
>
> if there were any interesting restrictions or corner cases you ran into

I would say the most interesting part of the implementation so far to me was the l[oadable-vs-indirect distinction](https://github.com/swiftlang/swift/pull/90247) with strongs-vs-weaks and tracing that back to the Clang's loadable decision in the llvm changes. Related but not the same (and in cxx interop mode): [#93040](https://github.com/swiftlang/swift/issues/93040)

As well, and probably worth review is the [bridging design](https://github.com/swiftlang/swift/pull/92784). Right now I make the bridged types available as public computed properties on the structs where the original stored vars become private to Swift clients. Therefore the original stored objc type still defines the layout but is private where the Swift type becomes a public computed var with `{ get set }` access.

---

<div class="post-metadata">

**Author:** ![AdamCmiel](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/adamcmiel/32/15453_2.png) [@AdamCmiel](https://forums.swift.org/u/AdamCmiel)\
**Post date:** [October 15, 2026, 1:37am UTC](https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059/14 "2026-10-15T01:37:09Z")

</div>

Two implementation surprises emerged around returns: ObjC methods must treat these non-retainable records as `Owned`: [[SIL] Take ownership of non-retainable record returns from ObjC methods by AdamCmiel · Pull Request #93078 · swiftlang/swift · GitHub](https://github.com/swiftlang/swift/pull/93078), while C functions must model them as indirect SIL results even when Clang returns them directly: [[ClangImporter] Import C structs with strong ARC pointer members by AdamCmiel · Pull Request #64381 · swiftlang/swift · GitHub](https://github.com/swiftlang/swift/pull/64381/changes/639b7806c5fad77b4cd54f1f5716f4bcca6ef37b)

---

<div class="post-metadata">

**Author:** ![John\_McCall](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/john_mccall/32/1351_2.png) [@John\_McCall](https://forums.swift.org/u/John_McCall)\
**Post date:** [October 15, 2026, 6:30am UTC](https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059/15 "2026-10-15T06:30:14Z")

</div>

The latter is surprising. We should model imported C types as loadable in SIL if Clang says they can be passed in registers; I can imagine that not being set correctly right now.

We already know how to actually pass/ return “direct” parameters/results indirectly if the C ABI says that’s how it works for a specific function type, like when the type is very large (or the psABI is inefficient about aggregates).

---

<div class="post-metadata">

**Author:** ![AdamCmiel](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/adamcmiel/32/15453_2.png) [@AdamCmiel](https://forums.swift.org/u/AdamCmiel)\
**Post date:** [October 15, 2026, 1:13pm UTC](https://forums.swift.org/t/pitch-import-c-structs-with-arc-pointer-members/64059/16 "2026-10-15T13:13:41Z")

</div>

You know, you're right, we don't actually need the indirection here in SIL. I had pulled the indirect-result handling from an older, separate investigation and I don't think I pushed on the indirect at the time as I was targeting the generated clang ctor/dtor instead of generating a per-field VW.

For the supported strong-only records, the SIL result certainly should remain direct and `@owned` and IRGen already lowers it to either registers or `sret` according to Clang's ABI classification. I've updated the patch and added coverage for both paths, as well as nested records and C function pointers. Let me know what you think!
