Remove Some Customization Points from the Standard Library's
- Proposal: SE-0232
- Author: Ben Cohen
- Review Manager: Ted Kremenek
- Status: Implemented (Swift 5)
- Implementation: apple/swift#19995
- Review: Discussion thread, Announcement thread
This proposal removes four customization points from protocols in the standard library:
The default implementations of these symbols will remain, so sequences and collections will continue to have the same operations available that they do today.
Swift-evolution thread: Discussion thread topic for that proposal
Customization points are methods that are declared on the protocol, as well as given default implementations in an extension. This way, when a type provides its own non-default implementation, this will be dispatched to in a generic context (e.g. when another method defined in an extension on the protocol calls the customized method). Without a customization point, the default implementation is called in the generic context.
This serves broadly two purposes:
Allowing for differences in behavior. For example, an "add element" method on a set type might exclude duplicates while on a bag it might allow them.
Allowing for more efficient implementations. For example,
counton forward-only collections takes O(n) (because without random access, the implementation needs to iterate the collection to count it). But some collection types might know their
counteven if they aren't random access.
Once ABI stability has been declared for a framework, customization points can never be removed, though they can be added.
Customization points aren't free – they add a small cost at both compile time and run time. So they should only be added if there is a realistic possibility that either of the two reasons above apply.
In the case of the customization points in this proposal, reason 1 does not apply. In fact it could be considered a serious bug if any type implemented these features with anything other than the default observable behavior.
It is also hard to find a good use case for reason 2 – whereas slight slowdowns
and code size bloat from the presence of the customization points have been observed.
In some cases (for example
suffix(from:)), the implementation is so simple that
there is no reasonable alternative implementation.
While it is possible that a resilient type's
forEach implementation might be able
to eke out a small performance benefit (for example, to avoid the reference count
bump of putting
self into an iterator), it is generally harmful to encourage this
kind of "maybe forEach could be faster" micro-optimization. For example, see
here, where error control flow was
used in order to break out of the
forEach early, causing unpleasant
interference for debugging workflows that detected when errors were thrown.
Future move-only type considerations
In the case of
last there is an additional consideration: in the
future, collections of move-only types (including
Array) will not be able
to reasonably fulfil these requirements.
A collection that contains move-only types will only allow elements to be
either removed and returned (e.g. with
popLast()), or borrowed (e.g. via
Returning an optional to represent the first element fits into neither of these
buckets. You cannot write a generic implementation of
first that fetches the
first move-only element of a collection using a subscript, moves it into an
optional, and then returns that optional.
last need to be removed as requirements on the
collection protocols in order to make it possible for collections of move only
types to conform to them.
They would remain on
Collection via extensions. When move-only types are
introduced, those extensions will be constrained to the collection element
Once the final functionality for move-only types is designed, it may be that
language features will be added that allow for borrowing into an optional,
allowing even collections of move-only types to implement a
But it's better to err on the side of caution for now and remove them from
Remove these customization points from the
Collection protocols. The
default implementations will remain.
These are customization points with an existing default implementation, so there is no effect on source stability.
It is theoretically possible that removing these customization points could
result in a behavior change on types that rely on the dynamic dispatch to add
additional logic. However, this would be an extremely dubious practice e.g.
MyCollection.first should really never do anything more than return the first
Effect on ABI stability
Removing customization points is not an ABI-stable operation. The driver for this proposal is to do this before declaring ABI stability.
Effect on API resilience
Leave them in. Live with the slight code/performance impact in the case of
forEach, and work around the issue when designing move-only types.