The "Safe Random Access Collection Element" thread is yet another why-can't-we-get-to-another-collection-element-safely thread. I came up with sample methods to do that in two posts. Maybe we should pull the trigger and finally add them.
Basically, the problem is the need for custom boilerplate code to safely traverse amongst indices. The index part of the Collection
API mostly does not use the usual way to confirm failure: nil
returns. The one method that does is too complicated for general use. Outside of Collection
wrappers, I've only used index(_: offsetBy: limitedBy)
with a limit that isn't either endIndex
or startIndex
a few times. And you have to explicitly mention which limit to use based on the sign of the offset amount. The new "elementIndex
" API always gives you either an Index
value that can be dereferenced to a Element
, or nil
. There's versions for a general offset, one step forward, and one step backward from a known-good index value.
There's a fourth version that is a general offset from startIndex
. It can be used instead of providing an oft-requested I-want-to-subscript-from-0..<count
member. The advantage of easing Index
-to-offset conversion is Index
-dereferencing is O(1) while direct offset subscripting is linear for collections that are not random-access.
That last capability could be considered a counter to the (fortunately?) suspended SE-0265 proposal. The new methods may lessen the "holding it wrong" foot-gun the current indexing API has, without going to the accidentally quadratic... accidentally quadratic everywhere leg-shotgun of SE-0265. (And SE-0265 has correctness issues of what happens if at least one coordinate of an offset range isn't valid.)
On a separate related note, maybe we could add a single-element replacement API:
extension RangeReplaceableCollection {
/// Replaces the element at the given index for one with the given value.
///
/// Calling this method may invalidate any existing indices for use with
/// this collection.
///
/// - Parameters:
/// - i: A valid index of the collection. Must be less than `endIndex`.
/// - other: The new element to add to the collection.
/// - Returns: The element value that was originally at `i`.
/// - Postcondition: Consider *d* to be `distance(from: startIndex, to: i)`
/// before the call to this method. Now consider *j* to be
/// `index(startIndex, offsetBy: d)` after the call to this method. Then
/// `self[j]` is the same as `other`.
///
/// - Complexity: O(*n*), where *n* is the length of the collection.
@inlinable
@discardableResult
public mutating func replaceElement(at i: Index, with other: Element)
-> Element {
defer {
replaceSubrange(i..<index(after: i), with: CollectionOfOne(other))
}
return self[i]
}
}
This could "work around" String
not having a direct element-level setter.