It would be wise to get in the habit of writing those sorts of constructions with if let:
if let start = index {
// [...]
}
Not only is it more concise, but as soon as concurrency enters the picture, other parts of the program can change the value in between the two lines:
if x.y.z != nil {
// What if some concurrent part of the program modifies x, y, or z?
let a = x.y.z! // ...crash...
// [...]
}
@mayoff is correct, this is invalid:
guard let range = haystack/*[...]*/.range(of: /*[*/"tag"/*]*/) // [...]
haystack.replaceSubrange(range, with: "tag1")
guard let range2 = haystack[range.upperBound...] // [...]
- The first line gets a range (start and end indices) that describe the original state of
haystack.
- The second line modifies
haystack behind the backs of those indices.
- The third line then applies the
upperBound index to a mismatched string. If it doesn’t crash, you are just lucky.
The problem can be demonstrated with a simple modification to the parameters:
var haystack = "long search term..."
let needle = "long search term"
foo(haystack: &haystack, needle: needle) // error: EXC_BAD_INSTRUCTION
In that case it happens because the range is equivalent to 0–16, the mutation shortens the string from 19 characters down to 7 (“tag1...”) and then the upper bound of 16 is out of bounds when you try to use it.
It may be tempting to just try to compute the difference in length, but that is not actually predictable, since characters can combine ("e" + "́" is 1 + 1 = 1). The only safe way to do replacement is to build a separate String instance by consecutively appending fragments, all without touching the original String, so that its indices remain valid. Then, when the new string is complete you can replace the old with it in a single overwrite.
That is why the extension I provided above does it the way it does; there are no safe shortcuts. (But you could simplify it to a concrete method for String if you want, you do not necessarily need the generics that allow it to also work with Arrays and other Collections.)