Library evolution mode: which types need @frozen to keep ABI stability across framework updates
0 reputation · 04 Feb 2026, 14:11 UTC
0 reputation · 04 Feb 2026, 14:11 UTC
I'm maintaining a small Swift framework consumed by an application that must be updatable without recompiling or restarting dependent clients. The framework is built with library evolution mode enabled, and I understand Swift 5's ABI stability lets the app run against a newer Swift runtime as long as the deployment target is iOS 12.2 / macOS 10.14.4 or later.
What I can't resolve is the resilience boundary for my own public types. My understanding is that structs and enums default to resilient under library evolution, and that marking them @frozen trades future flexibility for performance, while @inlinable code can silently bake implementations into client binaries and break compatibility if the implementation later changes. The unresolved part is generic types with associated-type-like constraints, where the resilience model seems less clearly specified.
My constraints: some hot-path value types would benefit from frozen layout, but I may need to add stored properties in a future release, and I cannot force clients to redeploy.
Given that, my questions are:
@frozen (or must avoid it) to guarantee binary compatibility across framework updates?otool -L and runtime testing, that a given public API surface is ABI-safe before shipping?35275 reputation · 04 Feb 2026, 20:50 UTC
To guarantee binary compatibility across framework updates in Library Evolution mode, you must avoid @frozen on any type where you anticipate adding stored properties or enum cases in the future. In Swift, no public type "requires" @frozen to be ABI-safe; rather, @frozen is an optimization that explicitly disables resilience.
@frozen): This tells the compiler the layout will never change. The client can bake the exact memory offsets into its binary for performance. If you add a property to a @frozen type in a later update, any client not recompiled against the new version will likely crash due to memory misalignment.Generic types and those with associated-type constraints follow the same resilience rules as standard structs and enums. There is no separate "manual-annotation" requirement specifically for generics to make them resilient; they are resilient by default unless marked @frozen. If a generic struct is non-frozen, the compiler handles the layout indirection regardless of the constraints applied to its generic parameters.
Beyond otool, you can verify the resilience of your public API using the following approach:
BUILD_LIBRARY_FOR_DISTRIBUTION = YES is set in your Xcode build settings.swiftc -emit-module -module-name YourFramework YourFile.swift
Search the output for the frozen keyword. If a type is not explicitly marked frozen in the emitted module, it is resilient.| Type Goal | Annotation | ABI Impact of Adding Property/Case |
|---|---|---|
| Future Flexibility | None (Resilient) | Safe / Compatible |
| Maximum Performance | @frozen |
Breaking Change |
| Inlined Logic | @inlinable |
Potentially Breaking (Logic is baked in) |
Missing Detail: Are you using @inlinable methods that access stored properties of these types? If so, the resilience of the type is bypassed by the inlined code, which may require a different strategy to avoid crashes.
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 04 Feb 2026, 22:39 UTC
When a public struct or enum is marked @frozen under library evolution, the compiler treats not only its stored‑property layout as fixed but also the exact count and order of its generic parameters. Consequently, you cannot add, remove, or reorder generic parameters—or introduce new associated‑type constraints—without breaking existing clients. Adding new protocol conformances that do not introduce stored properties is still safe, because they do not affect the frozen layout.