You need to add a field to a public struct in a binary SPM library used by five apps. What do you consider?
Mental model. You are changing the shape of a shipping container while five trucks are already on the road. Either the trucks do not care, or they must all come back.
First, is the type resilient or @frozen? Resilient (library evolution on, non-frozen) hides layout, so adding a stored property can be ABI-compatible. Frozen means no — you have promised the layout.
Then consider: default values for existing initializers, whether the struct crosses @inlinable boundaries, Codable compatibility (old payloads lack the key), and whether identity really wanted a class.
Sometimes the right move is a new type plus deprecation, not a silent layout change. Ship a migration guide and a minor version if source breaks; never a patch.
Common traps
- Adding a non-optional field with no default. Every client initializer call breaks at compile time — and every existing payload breaks at decode time.
- Assuming “it compiles here” means it is safe. Binary compatibility is about clients you cannot see, built with older compilers.
- Changing the field order for a frozen struct. That is an ABI break even though the source looks identical.
They might follow up with
- How do you add a field without breaking Codable? Make it optional, give it a default in a custom
init(from:), or version the payload. - How do you test ABI compatibility? Compare the generated
.swiftinterfaceand symbol graph between releases; run client smoke tests against the old binary.
Related questions
- 1.10.4 What is ABI stability vs source compatibility vs module stability?
- 1.10.5 What does @frozen on an enum mean for you as an API author?
- 1.10.1 How does Codable synthesis work? When do you write CodingKeys or a custom init(from:)?
← What does @frozen on an enum mean for you as an API author? How do you set a language bar for a large iOS org (Swift 6… →