The Secret Life of Swift Subscripts

Valeriy Bragin
The Startup
Published in
4 min readOct 25, 2020
Photo by Stefan Steinbauer on Unsplash

Many programming languages have a shortcut syntax of square brackets to directly access the value in array or dictionary. In Swift this mechanism is more flexible and generic and is called subscripts. You can create custom subscripts for any type, using indices of any type and arbitrary amount. This article though is not about subscripts in general, but about one somewhat opaque detail of its behaviour I’ve come across and wanted to share.

Assume you have an array of strings, and suddenly you realize that one of the values in it isn’t exactly what you need, so you have to adjust it. It’s well-known that you can not fix it the following way, due to value semantics of String:

When dealing with value types, we need to remember that each time we assign its instance to a new variable, or return value from function, or from get, we get a copy of this variable (actual copying may be delayed or optimized due to copy-on-write technique, but fundamentally this makes no difference here). And modifying this copy doesn’t anyhow affect it original value . So Roy Jones in original array left intact. All what we did is we copied its value (only value, not capturing its identity, which value types actually don’t even have by default) to another variable and modified this variable. So output still says:

[“Bernard Hopkins”, “Roy Jones”, “Mike Tyson”]

Will something change if we re-write it in shorter way? Instead of creating local variable, let’s just change the value in-place, not using any additional variable.

Will it help? Well, it hardly will... That’s what the swift engineer’s common sense would say, and I’m inclined to agree with it. The get-part of subscript of Array we are using here is just a shortcut for a method that returns a value. With value semantics such operation doesn’t make any sense —we’re getting the copy, modifying it and immediately we loose it, because it is not even stored in any local variable. So we should at least get the warning about this wasteful and meaningless operation. And furthermore, the closer look at this suggests that we will end up with error. Methods returning value types always return non-mutating version (something like let rather than var), so we must be getting something like ‘cannot use mutating append on immutable value’. Anyway, let me try and build the code above to see what will happen.

[“Bernard Hopkins”, “Roy Jones Junior”, “Mike Tyson”]

Oops. Hmm. It builds, no warning, and … it even works! It changed the value directly in the array, which is really strange. My common sense and I look at each other puzzled. Documentation of array gives no hint to explanation of such behaviour. Apple says: that subscript in Array is a regular read-write subscript, we swear! So behind the facade it should use the regular getter, pretty much like we do with computed properties or any function that returns something. But we see that instead of returning the value (which should be immutable for value-type String) this subscript does something that can be schematically put like this: it returns mutable value for us to to mutate it with append function on the fly, and then it sets it back to array, so that array gets updated with this new value. And this all happens in one line…

Only the look into swift source code for Array sheds the light at this dark story. It turns out that the subscript, despite what is explicitly stated in documentation, uses neither get nor set blocks, but there is another one -- _modify. And this is obviously the entry point to a mechanism that makes this tricky on-the-fly mutation happen.

Part of implementation of Array in Swift standard library

This mechanism is not yet exposed to mere mortal swift developers, but we’ve picked up its trail, and this might have been the end of our story. But one may legitimately ask: can I replicate such functionality in my custom code? Well the short answer is yes, technically you can. Remember though that this _modify is undocumented, so you’ll be using it at your own risk. But if you are decided, you should be ready to set your foot on the hazardous terrain of generators, coroutines, yield (which is also undocumented operator by the way).

P.S. It is disputable whether Swift should officially acquire this modify and yield functionality. Probably it’s being kept private by design, probably it’s already on the track but not yet ready for prime time. Some in Swift community say, however, that this would be too much for already inflating language. Personally, I feel somewhat uncomfortable with this disguised behavior of alledgedly read-write subscript. So I’d vote either for making it documented and legal, or for refraining from using it in standard library public API (although implicitly, it is indeed used in the API). And what is your view on that? Was such behaviour also surprising for you, and do you think it is consistent? Share your thoughts in comments, and see you next time.

--

--