Struct Memory Layout

Hello!

I'm writing a library (currently for my own personal use) that interfaces with a C-level API that takes in a pointer to a variable buffer. I wanted to include type definitions, so I built an API that binds the memory to a user-supplied struct type that is meant to represent the memory layout (e.g. the user defines the struct in Swift on their end and passes it in as a type). It works as I expect on my MacBook, and thus I assume that structs in Swift have a contiguous memory layout, with members laid out in the order they are defined (at least on Apple platforms).

However, scouring through first-party Swift documentation, I can't find anything that explicitly says this, save for this document that hasn't been changed in 6 years and has a concerning "orphan" tag at the top:

I'm curious if this documentation is still accurate. It also mentions, specifically, "fragile structs". Is that an important distinction? If so, what does it actually distinguish?

1 Like

The only supported way to ensure that a struct has a C-compatible layout is to define it in C and then import it into Swift, AFAIK.

7 Likes

Even in case this document per se is not outdated, the layout it describes is not a public guarantee.

What you should do is declare the required structs in a C header, then import it. Swift will have to respect this layout then.

This post sheds more light.

2 Likes

I've definitely read that. That's not really a friendly DX-experience though, especially when dealing with dynamic buffers. Granted, I'm the only one using it (for now, at least).

I've seen this reply. It is definitely informative, but it was last updated in 2019, and is from a third party (not the Swift team directly). Those lead me to wonder how valid or accurate it is now, 5 years later.

To follow-up on my previous reply, how would this work exactly?

In Swift Package Manager, is it possible to define a C struct inside a C file and then use that struct in a Swift package, all within the same target? Or do they have to be separate targets?

EDIT: I think I figured it out using separate targets. Not ideal, but it does give me insight into how that would work.

Does C guarantee struct layout? I thought it's compiler dependent.

I think you're right. But the intended use of this C-level API I am using is to define a struct of variable data and then pass a pointer into one of the functions. I'm more just wondering about the pitfalls of doing that with Swift pointers, and if it would cause unexpected issues.

IIRC, the only things Swift guarantees the layout of are imported C types and homogeneous tuples.

Edit: And also, technically, I think structs with a single member are guaranteed to have the layout of that member, but the compiler would have had to go out of it's way to not guarantee that.

I am surprised that we haven't created a syntax that can ensure this --

@c struct MyStruct {
}

or

@convention(c) struct MyStruct {
}

To ensure this.

5 Likes

I’ve been lurking on the Swift forums for a long time, but this is my first post. I’ve been developing iOS apps with Swift since 2015, but I’ve only recently started learning about lower-level features and C interoperability. I don’t have a comp-sci background, so I am new to working directly with memory.

After spending some time learning about the Unsafe…Pointer apis, I came across a stackoverflow question asking about something similar to what you are trying to do: pass a pointer to a C struct to a C function as a buffer to be populated by the function.

As a challenge to myself, I made a wrapper class that does this. Here’s it is with an example of its usage: A pure swift buffer for use with setattrlist and getattrlist · GitHub

In this case, my abstraction is tailored for use with the bsd getattrlist and setattrlist functions, but I think the concept can be generalized.

And here’s the stackoverflow question that inspired it: c - Change kMDItemDateAdded ( "Date added" ) file's metadata attribute via Swift code - Stack Overflow

The basic idea is that if the C function takes an untyped pointer to the buffer it is populating, it doesn’t actually have to be a C struct - it can be an UnsafeMutableRawPointer, and you can read the function’s documentation to find out how the data is structured, then retrieve it from the pointer’s offsets. C doesn’t actually have to see the interface at all, so you don’t have to try to make a swift struct that can be imported by C.

2 Likes

There is an existing but undocumented and unstable @_cdecl attribute that does whatever the current definition of “C-compatible layout” means today. There have been several pitches to formalize this attribute.

1 Like

You cannot apply @_cdecl to struct declarations. There is no current way, underscored or not, to guarantee C member layout in Swift unless you go through a C header.

9 Likes

We have a stable ABI these days so @frozen structs declared in Swift do indeed have a defined layout — it’s just not necessarily the same as the equivalent struct declared in C, though, just like the Swift calling convention sometimes differs from that of C even if the parameter and return types match up.

I think this guarantee is actually one difference between Swift and C. IIRC they differ in whether one-element structs are treated as their element in the calling convention. I’ll check later but I think C is the one that doesn’t always “unwrap” the field.

9 Likes