[swift-evolution] Pitch: Range return values for String Insert/Replace methods

Charles Srstka cocoadev at charlessoft.com
Tue Mar 14 11:21:57 CDT 2017


MOTIVATION:

Swift strings support a few methods for inserting and replacing things in the middle of them:

mutating func insert(_ newElement: Character, at i: String.Index)

mutating func insert<S : Collection where S.Iterator.Element == Character>(contentsOf newElements: S, at i: String.Index)

mutating func replaceSubrange(_ bounds: ClosedRange<String.Index>, with newElements: String)

mutating func replaceSubrange(_ bounds: Range<String.Index>, with newElements: String)

mutating func replaceSubrange<C where C : Collection, C.Iterator.Element == Character>(_: ClosedRange<String.Index>, with: C)

mutating func replaceSubrange<C where C : Collection, C.Iterator.Element == Character>(_ bounds: Range<String.Index>, with newElements: C)

These work well, but sometimes is advantageous to know the range of the portion of the string taken up by the inserted elements—for example, if one is creating a UI element, one may want to highlight the inserted text. In the old days when strings were defined in terms of UTF-16 code units, this was easy enough—just make a range starting at the insertion point and with the length of the string you just inserted. In Swift it is awkward and verbose:

let insertedRange = replacementRange.lowerBound..<string.index(replacementRange.lowerBound, offsetBy: replacementString.distance(from: replacementString.startIndex, to: replacementString.endIndex))

PROPOSED SOLUTION:

Introduce a return value on the insert and replace methods that contains the range of the inserted characters or string:

@discardableResult mutating func insert(_ newElement: Character, at i: String.Index) -> Range<String.Index>

@discardableResult mutating func insert<S : Collection where S.Iterator.Element == Character>(contentsOf newElements: S, at i: String.Index) -> Range<String.Index>

@discardableResult mutating func replaceSubrange(_ bounds: ClosedRange<String.Index>, with newElements: String) -> Range<String.Index>

@discardableResult mutating func replaceSubrange(_ bounds: Range<String.Index>, with newElements: String) -> Range<String.Index>

@discardableResult mutating func replaceSubrange<C where C : Collection, C.Iterator.Element == Character>(_: ClosedRange<String.Index>, with: C) -> Range<String.Index>

@discardableResult mutating func replaceSubrange<C where C : Collection, C.Iterator.Element == Character>(_ bounds: Range<String.Index>, with newElements: C) -> Range<String.Index>

The return value would contain the range of the portion of the string now occupied by what was inserted.

If this change is considered acceptable, it could also be applied to the equivalent methods on RangeReplaceableCollection.

IMPACT ON EXISTING CODE:

The @discardableResult attribute would ensure that existing code would continue to work as is. The change in the signatures, however, would affect ABI compatibility.

If the change is made to the protocols, however, this would affect source compatibility, as concrete types conforming to the protocols would need to be updated.

ALTERNATIVES CONSIDERED:

If there are performance concerns with generating the ranges, String could simply have two versions of each method, one returning a range and one returning Void.

Charles



More information about the swift-evolution mailing list