Tim Vermeulen Tim Vermeulen - 1 year ago 84
Swift Question

MutableCollection vs RangeReplaceableCollection

From Apple's

API reference:

protocol allows changing the values of a collection’s elements but not the length of the collection itself. For operations that require adding or removing elements, see the
protocol instead.

requires the following subscript:

subscript(bounds: Range<Self.Index>) -> Self.SubSequence { get set }

Doesn't this allow changing the length of the collection? For instance, couldn't we call this subscript's setter with an empty range and a non-empty subsequence?

Answer Source

First note that you don't have to implement

public subscript(bounds: Range<Index>) -> MutableSlice<Self>

in your own collection because it has a default implementation in a protocol extension.

As one can see in the source code of that method, the subscript setter calls a

internal func _writeBackMutableSlice()

function which is implemented here. That function first copies the common number of elements from the slice to the destination range, and then verifies that the subscript range and the new slice have the same length:

    selfElementIndex == selfElementsEndIndex,
    "Cannot replace a slice of a MutableCollection with a slice of a smaller size")
    newElementIndex == newElementsEndIndex,
    "Cannot replace a slice of a MutableCollection with a slice of a larger size")

So you cannot change the length of a MutableCollection via the (default) subscript setter, and trying to do so will abort the program.

As an example, let use define a "minimal" type conforming to MutableCollection:

struct MyCollection : MutableCollection, CustomStringConvertible {

    var storage: [Int] = []

    init(_ elements: [Int]) {
        self.storage = elements

    var description: String {
        return storage.description

    var startIndex : Int { return 0 }
    var endIndex : Int { return storage.count }

    func index(after i: Int) -> Int { return i + 1 }

    subscript(position : Int) -> Int {
        get {
            return storage[position]
        set(newElement) {
            storage[position] = newElement

Then replacing a part of the collection with a slice of the same length works:

var mc = MyCollection([0, 1, 2, 3, 4, 5])
mc[1 ... 2] = mc[3 ... 4]
print(mc) // [0, 3, 4, 3, 4, 5]

But for different lengths, it aborts with a runtime exception:

mc[1 ... 2] = mc[3 ... 3]
// fatal error: Cannot replace a slice of a MutableCollection with a slice of a smaller size

Note that concrete types conforming to MutableCollection may allow different-lengths replacement in their subscript setter, as for example Array does.