I just put up the draft proposal for Borrowing Accessors, which together with Yielding Accessors completes the full suite of accessor support described in the Prospective Vision for Accessors. More complete details are in the draft proposal. The following summarizes the goal of this ongoing effort:
Conceptually, borrowing accessors return a “borrow” of a value, providing either mutable or immutable access to some stored value. Swift typically implements “borrow” similarly to a C++ reference or C pointer.
As detailed in the Prospective Vision, this gives you a suite of approaches for implementing property access. For example, considering just immutable (read-only) access, you will now have these options:
Get (since Swift 1.0)
struct S {
var foo : Foo {
get {
// ... return a `Foo` instance
}
}
A plain get allows you to return an instance of the declared type. This instance can be a copy of a stored value or can be freshly constructed. In particular, this accessor can construct and return a non-copyable value, but cannot provide access to a non-copyable value stored in memory.
Borrow (this pitch)
struct S {
var foo : Foo {
borrow {
// ... return an existing `Foo` instance
}
}
}
A borrow accessor returns a “borrow” of a value that must outlive the execution of the accessor. Borrows can efficiently provide access to existing values, even if they are large or non-copyable. The compiler will verify that callers of this accessor do not extend their use of the borrow inappropriately. (For example, the compiler will not allow the caller to modify the containing object while they are using the borrowed property value.)
Yielding Borrow (SE-0474)
struct S {
var foo : Foo {
yielding borrow {
// ... yield access to a value
// ... after the caller is done, resume here
}
}
}
A yielding borrow uses coroutine semantics to “yield” the value to the caller, suspending the execution of the accessor until it is continued by the caller. This allows the provider to not only do work before yielding the value, but to also do work after the caller is finished using the value. Swift’s standard library uses an early version of this for its Dictionary subscript and will likely adopt the standard form as soon as the implementation is complete. This can also be used to set and clear markers for dynamically identifying access conflicts.
For callers, the distinction between different accessor types is mostly invisible. The caller just reads or writes the value. The only time this might impact the caller is if they try to simultaneously access multiple properties on a single value at the same time.
For providers, these three options provide a trade-off that can impact how you design your types and protocols. At the risk of over-simplifying:
getis the simplest,borrowis the most efficient, andyielding borrowis the most flexible.
Of course, there are also mutating counterparts to each of the above: set, mutate, and yielding mutate.