Why am I getting a simultaneous read&write error here?

When testing this extension method to post on another thread:

extension RangeReplaceableCollection {

    /// Removes all the elements whose indices satisfy the given predicate.
    @inlinable public mutating func removeAllAt(locations shouldBeRemoved: (Self.Index) throws -> Bool) rethrows {
        var result = Self()
        result.reserveCapacity(count)
        for i in try indices.lazy.filter({ try !shouldBeRemoved($0) }) {
            result.append(self[i])
        }
        self = result
    }

}

I used this in my playground to test:

var phrase = "The rain in Spain stays mainly in the plain."
let vowels: Set<Character> = ["a", "e", "i", "o", "u"]
let vowelIndices = Set(phrase.indices.lazy.filter({ vowels.contains(phrase[$0]) }))
phrase.removeAllAt(locations: { vowelIndices.contains($0) })
phrase

But this was my original code:

var phrase = "The rain in Spain stays mainly in the plain."
let vowels: Set<Character> = ["a", "e", "i", "o", "u"]
phrase.removeAllAt(locations: { vowels.contains(phrase[$0]) })
phrase

The problem was my predicate caused a crash ("error: Execution was interrupted, reason: signal SIGABRT."), starting with:

Simultaneous accesses to 0x103fcd250, but modification requires exclusive access.
Previous access (a modification) started at  (0x103ff779c).
Current access (a read) started at:
0    libswiftCore.dylib                 0x00007fff73c28140 swift_beginAccess + 557
6    libswiftCore.dylib                 0x00007fff73bd5db0 specialized Sequence.filter(_:) + 582
7    libswiftCore.dylib                 0x00007fff73aabea0 Sequence.filter(_:) + 30
10   com.apple.dt.Xcode.PlaygroundStub-macosx 0x00000001019a41b0 main + 0
11   CoreFoundation                     0x00007fff482c1040 __invoking___ + 140

Why is my original code wrong? Or is there a bug in Xcode 10.3 playgrounds?

This line

phrase.removeAllAt(locations: { vowels.contains(phrase[$0]) })

You're mutating phrase, by virtue of removeAllAt, and while you're not done mutating (haven't returned from removeAllAt), you're accessing it using subscript, phrase[$0].

1 Like

If you just want to filter characters, then phrase.filter { /*predicate */ } does that by passing characters to the predicate rather than indices. That way the closure doesn't need to capture the phrase string.

If you really want to filter indices, then you would need to first do that outside of the mutating method so that the filter closure can safely capture and read from the string.