Hello, Swift Community.
The Language Steering Group would like to gather feedback on a prospective vision for accessors in Swift. Vision documents help describe an overall direction for Swift. The actual Swift changes for executing on the vision will come as a series of separate proposals, so concrete details (e.g., specific syntax, API names, etc.) are less important than the overall direction. There is more information about the role of vision documents in the evolution process here.
The text of the introduction of the vision follows. The vision is quite long, so if you find it interesting, please follow the link above to read more.
A Prospective Vision for Accessors in Swift
Swift properties and subscripts can be implemented by providing one or more "accessors" that retrieve or update the value. Most Swift developers are familiar with the get
and set
accessors that are used to define computed properties:
struct Foo {
var value: Int {
get { ... provide a value ... }
set { ... accept a value ... }
}
}
In this case, the get
accessor behaves just like a nonmutating
method that returns a value of the property's type, while the set
accessor behaves just a mutating
method that receives a value of the property's type as an argument.
The get
and set
accessors are ideal for implementing operations that copy the current value of the property:
let copy = myFoo.value // calls the get accessor for Foo.value
or that overwrite the current value of the property:
myFoo.value = 51 // calls the set accessor for Foo.value
Other kinds of operations can also be compiled in terms of get
and set
. For example, if you pass a computed property as an inout
argument:
myFoo.value += 10
Swift will use get
to initialize a temporary variable, pass that variable as the argument, and then write the new value back with set
:
var tmp = myFoo.value // calls the get accessor for Foo.value
tmp += 10
myFoo.value = tmp // calls the set accessor for Foo.value
However, this approach has significant problems. The biggest is that the get
accessor has to return an independent value. If the accessor is just returning a value stored in memory, which is very common for data structures, this means the value has to be copied. This is unfortunate on three levels:
- It adds the runtime performance and memory overhead of copying the inline representation of the value. For example, if the value is an
Array
, the internal buffer of the array must be retained. - It can make subsequent uses of the value less efficient. For example, if the value uses a copy-on-write representation like
Array
andString
do, mutating a copy is likely to dramatically less efficient than mutating a variable in place. (We will explain this in more detail later.) - It requires the value to be copyable at all, and so it inherently cannot work for values of non-
Copyable
type.
These problems are amplified when properties and subscripts need to be abstracted over. When Swift knows exactly how a declaration is implemented, the compiler can access it in the best way possible given the implementation. For example, if Swift can see that a property is stored, it can emit code to directly access that memory instead of calling an accessor. However, when Swift doesn't know how the declaration is implemented, it must call some kind of accessor instead, and so it is limited by the capabilities of that accessor.
This kind of abstraction is necessary in several common situations:
- when the declaration is being accessed through a protocol requirement,
- when the declaration is a non-
final
member of a class, or - when the declaration is from a different library that's been built with library evolution enabled.
For example, suppose we have this code:
struct Person: Nameable {
var name: String
}
protocol Nameable {
var name: String { get }
}
func printNameConcretely(_ person: Person) {
print(person.name)
}
func printNameGenerically(_ person: any Nameable) {
print(person.name)
}
In printNameConcretely
, Swift knows that name
is a stored property of Person
, and it can just load that value directly from person
and pass it to print
. In printNameGenerically
, Swift does not know how name
is implemented, and it must call a get
accessor to copy the current value of the name. To avoid those costs, the Swift optimizer would have to specialize this function for the specific type that is being passed in; this is something that Swift can and does do, but only as a best-effort optimization, which is not always good enough. And, of course, this code would be ill-formed if String
were a non-Copyable
type, because the only way to satisfy a get
requirement for a stored property is to copy the current value.
As a result, Swift has explored a variety of other accessors throughout its history, none of which have ever been officially added to the language through the Swift Evolution process. (The observing accessors, willSet
and didSet
, are officially in the language but are arguably in a different category because they don't serve as complete operations.) Many of these have been adopted in the standard library for years, but we've been reluctant to make them official because they are variously incomplete, unsafe, or complex.
This vision document lays out the design space of accessors for the next few years, as Swift continues to advance its support for non-Copyable
and non-Escapable
types. It explains Swift's basic access model and how it may need to evolve. It explores what developers need from accessors in these advanced situations. Finally, it discusses different kinds of accessors, both existing and under consideration, and how they do or not fit into the future of the language as we see it.
This is a prospective vision which has not yet been reviewed by the Language Steering Group. Even if it is approved by the Language Steering Group in exactly this form, it is merely laying out a high-level vision for the language design and does not constitute pre-approval of any specific ideas in this document. Everything in this document will need to be separately proposed and reviewed under the normal Swift Evolution process before it is part of the Swift language.