Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MutableCollection vs RangeReplaceableCollection

Tags:

swift

swift3

From Apple's MutableCollection API reference:

The MutableCollection 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 RangeReplaceableCollection protocol instead.

However, MutableCollection 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?

like image 532
Tim Vermeulen Avatar asked Jun 25 '16 22:06

Tim Vermeulen


1 Answers

Short answer:

If you have a variable of the MutableCollection type then you must call the subscript setter only with a range and a new slice having the same length. Some types conforming to MutableCollection (such as Array) allow a different-length replacement to insert or delete elements, but in general, a mutable collection need not allow that.

In particular, the default implementation of the MutableCollection subscript setter aborts with a runtime exception if the range and the new slice do not have the same length.

Longer answer:

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:

_precondition(
    selfElementIndex == selfElementsEndIndex,
    "Cannot replace a slice of a MutableCollection with a slice of a smaller size")
_precondition(
    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.

like image 108
Martin R Avatar answered Oct 04 '22 06:10

Martin R