Extensions are a useful way to organize related properties and methods within a source file. Stored instance properties, however, are currently not allowed to be written in an extension:
struct Planet {
let name: String
}
extension Planet {
var mass: Double // error: extensions must not contain stored properties
}
One common pattern is conforming to a a type to a protocol using an extension, and defining all of the members required by the protocol in that extension. Extensions currently aren't allowed to define stored properties, though, so organizing code in this way is not always possible:
protocol AstronomicalObject {
var mass: Double { get }
var parent: AstronomicalObject? { get }
func terraform()
}
struct Planet {
let name: String
}
extension Planet: AstronomicalObject {
var mass: Double // error: extensions must not contain stored properties
let parent: AstronomicalObject? // error: extensions must not contain stored properties
func terraform() { /* add an atmosphere, which increases the mass */ }
}
Instead, the stored properties must be defined in the type's base declaration:
struct Planet {
let name: String
let atmosphere: Atmosphere?
// Implements AstronomicalObject:
var mass: Double
let parent: AstronomicalObject?
}
extension Planet: AstronomicalObject {
func terraform() { /* add an atmosphere */ }
}
This limitation is mostly artificial. We could lift this restriction and allow stored properties to be defined in extensions, as long as the extension is in the same file as the type being extended. This would increase flexibility and consistency within individual source files.
I prototyped this change in apple/swift#61593 -- would love to hear what folks think!
Detailed design
We would allow stored properties in extensions of class
, struct
or actor
types which are defined in the same file as the type being extended. The example above would now be permitted to compile:
protocol AstronomicalObject {
var mass: Double { get }
var parent: AstronomicalObject? { get }
func terraform()
}
struct Planet {
let name: String
}
extension Planet: AstronomicalObject {
var mass: Double
let parent: AstronomicalObject?
func terraform() { /* add an atmosphere, which increases the mass */ }
}
These properties become part of the type's memory layout as if they were defined in the body of the type, and the ordering of the storage would be based on the ordering of the property declarations in the source file.
For struct
s, stored properties defined in extensions are included as parameters in the implicit synthesized constructor. Like with properties defined in the type body, the ordering of the parameters in the synthesized constructor declaration would be based on the ordering of the property declarations in the source file. For example:
struct Planet {
let name: String
}
extension Planet {
let mass: Double
}
let planet = Planet(name: "Earth", mass: 6e24)
Attempting to extend a type in a different file or module would not be allowed, since there would no longer be a clear, deterministic ordering for the type's stored properties. Attempting to do this would result in the following error:
extension String {
var identifier: String? // error: only extensions defined in the same file as the extended type are permitted to define stored properties
}
Stored properties are never allowed in enum
s or protocol
s, so they can't be permitted in extensions of these types:
enum PlanetaryBodyKind {
case planet
case moon
}
extension PlanetaryBodyKind {
let mass: Double // error: stored property is not permitted in extension of enum
}
protocol AstronomicalObject {
var mass: Double { get }
}
extension AstronomicalObject {
var parent: PlanetaryBody? // error: stored property is not permitted in extension of protocol
}
We also wouldn't allow stored properties in constrained extensions of generic types, since there isn't any design precedent for allowing a type to have different sets of stored properties based on the specific generic instantiation:
struct Generic<T> { }
extension Generic {
let t: T // Fine, since the extension is unconstrained and applied to all instantiations of `Generic`
}
extension Generic where T == Int {
let u: T // error: stored property is not permitted in constrained extension
}