edit: this has been updated for Swift as of Xcode 6.1 (Swift 1.1)
A common idiom in Ruby is to assign a value to a variable only if that variable isn’t already set, using the ||=
operator:
s ||= "default value"
Swift doesn’t provide such an operator out of the box, but using some of the features of the language, it’s possible to implement one.
Simple first implementation
First let’s try implementing it just for string types. We need to define the operator itself first:
infix operator ||= { associativity right precedence 90 assignment }
And then implement it with a function:
func ||=(inout lhs: String?, rhs: String) { if(lhs == nil) { lhs = rhs } }
Note the use of the assignment
attribute on the operator definition, and the inout
parameter on the left-hand side, which is the variable being assigned to.
This works. If you try the following
var s: String? s ||= "first assignment" s ||= "second assignment"
the second assignment will have no effect on s
– it will keep the value “first assignment”.
Using generics to apply to any type
What about implementing this with generics, so it will work not just for String but for any type? That’s pretty simple:
func ||=<T>(inout lhs: T?, rhs: T) { if(lhs == nil) { lhs = rhs } }
Now you can use our ||=
on any type optional type – String?
, Int?
, or any user-defined class.
Using autoclosure to avoid unncessary evaluation
Our ||=
still doesn’t quite match the Ruby version. If the value on the left-hand side is already set, the statement on the right-hand side is never executed. This is important if, for example, the right-hand side were a function with other side-effects, or an expensive computation.
But by default in Swift, any statement passed to a parameter is fully executed first. To replicate the Ruby functionality, we have to use an attribute in Swift called autoclosure
. This is used like this:
func ||=<T>(inout lhs: T?, rhs: @autoclosure () -> T) { if(lhs == nil) { lhs = rhs() } }
What autoclosure
does is wrap the arguments suppled in a closure, for later execution. Then, if they aren’t needed, they are never executed. If they are needed (in this case, because lhs
is nil
), the closure can be called (note the new type for rhs, and the parenthesis after rhs, indicating it is now calling a function).
To check this works, try the following, which should only print “I’ve been run” once to the console:
func printlnWhenRun() -> Int { println("I've been run") return 0 } var i: Int? i ||= printlnWhenRun() i ||= printlnWhenRun()
Implementing for Boolean values
This works great for optional types, but what about the more conventional behaviour, a compound logical-OR-and-assign operator for boolean values?
Despite listing it in the Expressions precedence section of the language reference, this doesn’t appear to be implemented in Swift natively.
edit: it’s now been removed from the precedence section. You can still declare it as below though.
No problem though, we can just implement it ourselves. Here it is:
func ||=<T: BooleanType>(inout lhs: T, rhs: @autoclosure () -> T) { if(!lhs) { lhs = rhs() } }
Note the BooleanType
type constraint after the type parameter. This is necessary because we need to guarantee that you can pass whatever type is used into the if
statement. This wasn’t necessary with the other version because it was the optional qualifier supplying this ability.1
But doesn’t this clash with the other definition? Nope. A combination of the type constraint on the logical version, and the presence of the optional parameter on the optional-assignment version, means the possible uses of these two functions are entirely disjoint, and the Swift compiler will pick the right one for you. Even a Bool?
type will go to the optional-assignment version.2
Using lazy properties instead of ||=
Having done all of this, you could argue ||=
isn’t all that useful in Swift because of different feature, lazy properties. These are properties of a class or struct that are only initialized for the first time when they are used – in much the same way as Ruby devs use ||=
. Below is some code showing this in action:
class MyClass { lazy var complexThing = CreateComplexThing() } var c = MyClass() // ... maybe some time later var thing = c.complexThing // CreateComplexThing() runs now
Obviously this only applies when your property is part of a class, not when you’re declaring a variable in a function, so maybe there’s still a place for ||=
. But if not, at least we learned something implementing it.
- And there’s no need to constrain to objects with the ability to assign to each other – they all have that ability. There’s no “Assignable” protocol. Be nice if there were – that would imply you could overload “=”. But this ain’t C++. ↩
- It is possible to write two generic functions that overlap in their inputs, in which case you will get an “use of unresolved identifier” error from the compiler when you call the function (note, not when you define the overlapping function, which is a break from other features of generics when you get a compiler error at the time of definition rather than use). ↩