Proposed change of plans for read accessors

Up to now, the plan for read accessors has been:

  • improve compiler support for them to ensure they're demonstrating their theoretical performance advantages
  • fix any bugs that arise as part of adopting read accessors more widely
  • switch the default opaque access strategy (used in e.g. protocol dispatch) over to use a read accessor instead of a getter
  • carve out an implicit exception so that getters are used for storage that produces sufficiently trivial/small types
  • carve out explicit exceptions (using an underscored attribute for now) for particular storage declarations (e.g. CustomStringConvertible.description) that are expected to roughly never return an existing value and thus benefit from using a read accessor

We've made a lot of progress on the first two, and I have a patch that does the third successfully (although it still causes a lot of test failures). As part of winnowing down the test failures, I started going through the stdlib to look at the last task: trying to find storage declarations that I should opt out and force to use getters. It turns out that there are a lot of protocol requirements that are pretty much destined to have computed implementations — beginIndex, most things in Codable, _NSSwiftValue.value, etc. — and relatively few protocol requirements that really ought to use a read accessor instead. In fact, it's at the point that I'm strongly inclined to switch the polarity of this work: leave getters as the default behavior and require storage declarations (at least in protocols) to opt in to using a read accessor in the opaque access pattern. I would then go through the stdlib and change specific declarations to use read accessors in their opaque pattern.

It has always been a goal of the ownership feature to allow the compiler to make a strong guarantee about certain accesses not causing semantically-visible copies (and ultimately even yielding the exact address of the storage). Making that guarantee through an opaque access requires the use of read accessors. Now, there are at least two reasons why some accesses simply cannot use read accessors: first off, @objc protocols exist, but even ignoring them, it was inevitable that we'd provide some way to force the use of getters on a protocol requirement, and that annotation necessarily inhibits the guarantee. So this change is just raising the profile of that exception quite a bit: it now requires that either the access is non-opaque or that it's only done through declarations that use opaque read accessors. But I think we can live with that.

The side-benefit of changing the plan is that I may have time to improve the ABI in some other ways, e.g. around key paths.


Going over the stdlib looking for places to adopt @_borrowed (strawman spelling; see Add a @_borrowed attribute to force the use of an opaque read accessor by rjmccall · Pull Request #20403 · apple/swift · GitHub or the associated forum thread), there's not much.

At least arguably required for move-only types:

  • Collection.subscript(Index) and its refinement on MutableCollection
  • _SequenceWrapper._base (unless this should go the opposite direction and become a consuming method)

The following are possible minor optimizations:

  • Error._domain, which will almost exclusively return a constant value — but using read in that case might not actually be an optimization over doing a no-op retain/release
  • RawRepresentable.rawValue, which I think will usually return a constant value, and which may need to be borrowed for move-only correctness
  • _AnyHashableBox._canonicalBox (not sure about this one at all)
  • Encoder.codingPath and KeyedEncodingContainerProtocol.codingPath
  • LazySequenceProtocol.elements and LazyCollectionProtocol.elements

FWIW, I plan to eliminate that particular type as part of SE-0234.


Should @_borrowed be added to the APIs introduced by SE-0205 and SR-3679?

No, @_borrowed as currently defined does something different. What you really want is some way of bypassing the normal argument-passing rules and forcing Swift to semantically borrow the argument.

1 Like