[Proposal] Opt-In Reflection metadata
- Proposal: SE-NNNN
- Authors: Max Ovtsin
- Review Manager: TBD
- Status: Pitch
- Implementation: apple/swift#34199
Introduction
Reflection can be a useful thing to create convenient and concise APIs for libraries. This proposal seeks to improve the safety of such APIs and to tackle the binary size problem by introducing a mechanism of selectively keeping reflection metadata only for types that need it and dead strip it for all others. Developers will gain an opportunity to express a requirement to have reflection metadata in source-code.
Motivation
API safety
Currently, it is impossible for APIs to express reflection metadata requirements which can lead to unexpected behavior. For example, SwiftUI implementation uses reflection metadata from user modules to trigger re-rendering of the view hierarchy when a state has changed. If by some reason a user module was compiled with the disabled metadata, changing the state won't trigger that behavior and will cause inconsistency between state and representation which will make such API less safe since it becomes a runtime issue rather than compile-time one.
Dead Stripping
The compiler can’t statically determine the uses of reflection metadata between the boundaries of the main binary and frameworks. Therefore all symbols must be no_dead_strippable
just in case other frameworks uses that metadata. Hereupon the linker can't strip such symbols which can lead to an increase of binary size especially in cases when API's surface is big.
Proposed solution
Introducing a new attribute @reflectable
for nominal types will help to solve two issues mentioned above at once:
-
Firstly, this will allow libraries to express a requirement to have reflection metadata for types on the source-code level which will make such APIs safer. If a declaration is marked with
@reflectable
or inherits it, but the opt-in reflection metadata is not enabled, the compiler will emit a compile-time warning suggesting to enable the feature. -
Secondly, this will help to reduce the binary size overhead from the metadata that is not used at runtime and possibly lead to other optimizations. The reflection metadata for nominal declarations (classes, structs, enums, and protocols) marked with
@reflectable
will be forced to stay at the binary while the other can be dead stripped by DCE or the linker if are not used within a module.
Study Case:
SwiftUI Framework:
@reflectable
public protocol SwiftUI.View {}
User module:
import SwiftUI
struct SomeView: SwiftUI.View {
var body: some View {
Text("Hello, World!")
.frame(...)
}
}
struct SomeModel {}
window.contentView = NSHostingView(rootView: SomeView())
Reflection metadata for SomeView
will be emitted with the no_dead_strip
label because it inherits the attribute from SwiftUI.View
protocol, while SomeModel
metadata might be stripped unless it’s referenced from somewhere else. If an API's user module gets compiled without the -enable-opt-in-reflection-metadata
flag, the compiler will emit a warning.
Detailed design
One more level of reflection metadata is introduced in addition to two existing ones:
- Full Reflection metadata is enabled by default.
- Opt-In reflection metadata is enabled explicitly with
-enable-opt-in-reflection-metadata
flag. - Reflection metadata is completely disabled with
-disable-reflection-metadata
flag.
A new attribute for nominal types @reflectable
marks nominal types for which the compiler will emit reflection metadata symbols with no_dead_strip
directive while other reflection symbols might be dead stripped by the linker in case they are not in use within a module.
The attribute should be inheritable from parents for classes and protocols:
Example 1:
@reflectable
protocol Ref {}
// Class A will inherit the attribute from the protocol conformance.
class A: Ref {}
Example 2:
@reflectable
class B {}
// Class C will inherit the protocol from the parent class.
class C: B {}
Source compatibility
The change won’t break source compatibility due to the purely additive nature of the change.
Effect on ABI stability
The proposal doesn’t change the existing ABI but introduces new guarantees between libraries and clients.
Effect on API resilience
Adding/removing the @reflectable
attribute won’t affect API resilience and break any existing guarantees. However, it’s still possible that some reflection metadata might be unavailable for cases when a library was updated with the new attribute, but a client wasn’t recompiled against a new version of the library.
Future direction
If this proposal gets accepted, it will allow extending the Swift Standard library with a new protocol Reflectable
that will be marked with @reflectable
attribute. All existing APIs that require reflection metadata might be extended to accept objects conforming to that protocol. It will help to improve the safety of such API because the compiler will ensure that for all types, conforming to the Reflectable
protocol, the required reflection metadata is discoverable at runtime.
@reflectable
public protocol Reflectable {}
public struct Mirror {
// A new constructor that takes an object conforming to Reflectable protocol.
public init(reflecting subject: Reflectable) {
...
}
}
Alternatives considered
An implementation with a magic protocol also was considered. However, It seems that an attribute will be a more generic approach in this case.