Let's start with an example of a simple class that represents a Point
in an x
, y
coordinate system (Cartesian). Consider the following:
class Point {
var x: Double
var y: Double
init(x: Double, y: Double) {
self.x = x
self.y = y
}
}
Now, let's define a simple translate
function that will mutate the x
and y
properties of the point
objects by adding dx
and dy
to x
and y
, respectively:
func translate(point : Point, dx : Double, dy : Double) {
point.x += dx
point.y += dy
}
Now, we can create a point instance with, for example, an initial value of 0.0
, and translate it to the position 1.0
:
let point = Point(x: 0.0, y: 0.0)
translate(point: point, dx: 1.0, dy: 1.0)
point.x == 1.0
point.y == 1.0
Because classes follow reference semantics, only a reference to the point object is passed to the translate function; x
and y
are defined as var
, and all of this code is valid.
Now, let's try to port our Point
class into a struct
. Consider the following:
struct Point {
var x: Double
var y: Double
}
We have defined a simple struct
; as you should notice, there's no need to add a constructor, as the compiler will synthesize it for us:
let point = Point(x: 0.0, y: 0.0)
translate(point: point, dx: 1.0, dy: 1.0)
If we keep the original implementation, our program won't compile. Point
is a value type now, and it's forbidden to mutate a value inside of a function! We need to add the inout
keyword to indicate that this function will mutate the contents of the value that is passed. When the function returns, the value will be assigned back to the original variable.
With those changes complete, we also need to change our call to indicate that our point
variable can be modified by our translate
function with the &
(ampersand) character. We also need to mark our point as var
; otherwise, the inout
function cannot modify its contents:
func translate(point: inout Point, dx : Double, dy : Double) {
point.x += dx
point.y += dy
}
var point = Point(x: 0.0, y: 0.0)
translate(&point, dx: 1.0, dy: 1.0)
point.x == 1.0 // true
point.y == 1.0 // true
We've successfully ported this function, but we can do better.
With structs, you will often see that this pattern is cumbersome. We may want the translate
function to return a mutated copy of the value we passed in, as follows:
func translate(point: Point, dx : Double, dy : Double) -> Point {
var point = point
translate(point: &point, dx : dx, dy : dy)
return point
}
We'll be able to use the previously defined function with the following code:
let point = Point(x: 0.0, y: 0.0)
let translatedPoint = translate(point, dx: 1.0, dy: 1.0)
point.x == 0.0
point.y == 0.0
translatedPoint.x == 1.0
translatedPoint.y == 1.0
With this new implementation, we're not mutating the value anymore, but the translate
function is always returning a new Point
value. This has many benefits, including the ability to chain such calls together. Let's add a method to our Point
struct:
extension Point {
func translating(dx: Double, dy: Double) -> Point {
return translate(point: self, dx: dx, dy: dy)
}
}
Note
You don't need to declare this new method in your struct
, but you can declare it anywhere in your program.
Using our newly crafted extension, we can easily create new Point
values and translate them:
let point = Point(x: 0.0, y: 0.0)
.translating(dx : 5.0, dy : 2.0)
.translating(dx : 2.0, dy : 3.0)
point.x == 7.0
point.y == 5.0