Sorry, but I have been waiting for months to make this pun. So today, we are going to talk about Operator overloading in Swift. This tool is very useful, but quite dangerous as well. The “With great power comes great responsibility,” quote is very appropriate for operator overloading. It can make your code a lot more concise, making even a function call seem like a 3-hour long lecture. But with that, you can also make the code nigh-unreadable to anybody that is new to your code.
Be very careful with this tool, and use it where it make sense, such as how “adding” two Swift Strings together makes a new string with one string first, then the other afterwards. You can make the addition operator print to the screen, make a network request, play music, or whatever else you could write a function for, but doing any of those would be a terrible idea in production code. You should do only what is necessary for the operator and nothing else. If you need different things like those, make an appropriately named function that makes it obvious that it will make a network request.
Whenever you think of whether to use operator overloading, you have to ponder whether it helps your code anymore than a simple function call. The function call may be longer, but it is a whole lot more expressive as to what it does (if named appropriately). If it looks like you are adding, subtracting, checking equality, or whatever else the built-in operators do though, and you do it enough in your code, Swift’s operator overloading may be the right tool for the job.
Operator Overloading
You should only use operator overloading in a way that is consistent with how the operator is normally used in Swift. Here is an example I thought of that could make sense, though there are still some pitfalls with it. Let’s say we want to find the amount of time between two Date objects. It would make sense to get that via “laterDate – initialDate” right? So let’s do that:
extension Date { static func - (left: Date, right: Date) -> DateComponents { Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: right, to: left) } }
In this case, I chose the Calendar.Components of year, month, day, hour, minute, and second as the components to use in this subtraction. Herein lies ones of the pitfalls. There are several other Components I could choose. If I just wanted to know the weeks between something, I could put that in instead for this. Nonetheless, using these units give us what we would want in most calendrical calculations. The next line just creates the DateComponents object using components:from:to: from the currentCalendar. We’re taking advantage of the fact that a single line of code that returns something can be used without explicitly writing return, because it is pretty clear to the compiler that a function that returns a date components being the only thing called in a function that also returns DateComponents would simply want to forward that return.
An operator used between two values is called an infix operator. There are two other overloadable types of operators known as prefix and postfix, like the old ++i and i++ operators of yore.
As you can see from the top, this is just a function in Swift, but instead of a text name, we have a symbol as the name. We specify the types of inputs for the left and right parts of the expression, and then specify the return type, which for this calculation should be an DateComponents object. Now we can use it like this:
let initialDate = Date() let components = DateComponents(year: 1, month: 5, minute: 42) let laterDate = Calendar.current.date(byAdding: components, to: initialDate)! //Test the overloaded operator difference = laterDate - initialDate //Results print(difference.year) //Outputs: "1" print(difference.month) //Outputs: "5" print(difference.day) //Outputs: "0" print(difference.hour) //Outputs: "0" print(difference.minute) //Outputs: "42" print(difference.second) //Outputs: "0"
There was a bit of setup there, but most of it should be pretty easy to understand. We first create an initial date, which has the value of the moment the Date object was created. Then there’s an DateComponents object that has its year, month, and minute values set. Then the laterDate constant is made, using Calendar’s dateByAddingComponents method, so that makes a value 1 year, 5 months, and 42 minutes in the future. Then we actually test our new capability for the ” – ” operator. When we look at the components of the result of our subtraction, you can see it has the correct value for the year, month, and minute value for exactly how far in the future it was.
I think that this function is a marginally okay use of operator overloading. It is using the subtraction operator how it is normally used in math. However, it hides several assumptions. I already mentioned that it uses certain Calendar.Components, but not all of them. Secondly, it uses Calendar’s current variable. I think this makes sense, but it is nonetheless hidden.
If we just use the normal function call that is behind our overloaded operator, you can clearly see what calendar it is using and which flags you want. That is why I only say that this is a marginally okay use of operator overloading. It makes sense, but it hides those assumptions from the reader, making them have to look up the operator overloading function to see what assumptions are made.
The Equatable Protocol
There are a few places in Swift where operator overloading is actually encouraged. If you want to be able to compare your custom class with the ” == ” operator, you will have to overload it. If you are going to implement this, your class should probably adopt the Equatable protocol. The Equatable protocol is used often with Generic functions (you can read more about them here: Generic Functions in Swift), when it will need to check for equality inside. If you do, you only need to implement the ” == ” operator, you get the != for free (since it is basically just negating the ” == ” operator’s answer.
The way you overload the ” == ” operator is the same as with any other infix operator, like what we did above with the ” – ” operator. Let’s make a custom type and overload the ” == ” operator below:
//Custom type itself class Temperature { let c: Double init(c: Double) { self.c = c } } //Custom Type's Extension extension Temperature: Equatable { static func == (left: Temperature, right: Temperature) -> Bool { if left.c == right.c { return true } else { return false } } } //Test let tempOne = Temperature(c: 15) let tempTwo = Temperature(c: 35) let tempThree = Temperature(c: 15) let oneTwoEquality = (tempOne == tempTwo) //Stores: false let oneThreeEquality = (tempOne == tempThree) //Stores: true
Now we can compare Temperature instances with the ” == ” operator. When an operator is overloaded, it must be marked as static. Actually, if I had made this a struct or enum, I wouldn’t even need the initializer or the operator overloading! If the parts in it are hashable or equatable, structs and enums can generate those methods themselves, without me needing to explicitly write them. The same is not extended to classes, and really, this type should’ve been a struct, but I wanted to show the behavior for a type that didn’t automatically get those methods when they adopt the protocol.
Another place where operator overloading in Swift is encouraged is with the Comparable protocol. To conform to the Comparable protocol you must implement an operator function for the ” < ” operator, as well as conform to the Equatable protocol. With you implementing == and > operators, the compiler can figure out the !=, <, <=, and >= operators for you. You can see an example of this in my previous post Generic Functions in Swift. It also has another example of overloading the ” == ” operator.
Conclusion
I debated showing the Date subtraction example above, due to how it hides many assumptions about the code being called. I decided to share it though, because it also highlights what you have to think about when deciding to use operator overloading in Swift. If you are doing subtraction of dates a lot in your code, always with the same unitFlags, overloading the subtraction operator could make your code a lot more readable.
There are a few operators that you cannot overload, most notably the assignment operator ” = ” (just one equal sign). Even if you could, I’m not sure I would want to know the chaos that would be wrought by doing so, considering how much the assignment operator is used. You can read more about the operators you can’t overload at Tammo Freese‘s article Facets of Swift, Part 5: Custom Operators.
Much like Custom Subscripts in Swift, this tool is very useful, but very dangerous. Use it wisely.
In updating this post, there are two very welcome updates to Swift that made the code for the “-” operator’s overload much nicer. Firstly, that you can use the most common Calendar.Components with their common names (.Year, .Month, etc.) instead of the longer original ones (.YearCalendarUnit, .MonthCalendarUnit, etc.). Secondly, the code used to use the OR operator ” | ” to combine the unit flags. Now, Swift lets you give it a Swift Set type for options of that sort, which itself can be created with a Swift Array literal.
I hope you found this article helpful. If you did, please don’t hesitate to share this post on Twitter or your social media of choice. The blog is still pretty new, and every share helps. Of course, if you have any questions, don’t hesitate to contact me on Twitter @CodingExplorer, and I’ll see what I can do. Thanks!