For the More Curious: Mutating Methods

I found this section rather dense and difficult to make my way through. Maybe because the concepts are a bit trickier to grasp than what is presented in the earlier parts of the chapter, I felt this could have used some more explaining and examples of some of the concepts that were rather glossed over.

In particular, this paragraph left me scratching my head:

Putting all of this information together, a mutating method is simply a method whose first argument is self, passed in as an inout parameter. Because value types are copied when they are passed, self is actually a copy of the value for nonmutating methods. To make changes, self needs to be declared as inout, and mutating is the way Swift allows you to accomplish that.

I thought this paragraph could have used a lot more explanation and examples. Maybe because it mentions self, whose use in this section is not given much attention. Or maybe because it involves an understanding of the difference between value types and reference types which have not really been covered yet at this point in the book.

Rather than confuse things further by presenting my own understanding of what I think this means, may I ask if anyone else encountered trouble with this section of the book? Or better yet, does anyone feel like they completely grasp all of the ideas presented in this section?

That paragraph seems to assume that you already know quite a bit.

I don’t have the book to check the previous material, but that paragraph should have made it clear that the self parameter is implicitly provided by the language, you can’t see it.

All non-static class, enum, and struct methods and properties have an implicit self parameter.

An example, modeling a point in 2D space, might help.

Point, the canonical way:

// 2D Point
struct Point {
    var x = 0
    var y = 0
    
    mutating func translate (dx:Int, dy:Int) {
        self.x += dx
        self.y += dy
    }
}

Although self is not (visibly) declared anywhere in the translate function above, we can still use it to refer to the instance of Point passed to the function. There is an invisible declaration (self:Point) in the parameter list. Of course, there will also be a corresponding invisible argument passed to the function at the point of a call; the invisible argument will be the instance of Point on which the function is invoked.

Point, with no instance methods, using a free function instead

// 2D Point
struct _Point {
    var x = 0
    var y = 0
}

func translate (point _self:inout _Point, dx:Int, dy:Int) {
    // using _self as the closest thing because can't use self in this context
    _self.x += dx
    _self.y += dy
}

Notice above that the first parameter of the translate function is a reference to an instance of Point itself, not to its copy. If you delete the inout qualifier, the original point will remain intact.

// Test drive
var p1 = Point (x:0, y:0)
print (p1)

p1.translate (dx: 3, dy: 5)
print (p1)

var p2 = _Point (x:0, y:0)
print (p2)

translate (point:&p2, dx:5, dy:5)
print (p2)

Thank you for the excellent explanation and examples. The book was referring to a code example similar to what you gave. Your comparison between the two examples is helpful. To clarify, is your use of the underscore in _Point and _self just a naming convention? Could you have just as easily used a different name here, such as Point2? Does _self parameter need to be named _self in order for the example code to work? I’m just wondering if the underscores have a special meaning here.

Yes. The underscore is mainly used to create neutral sounding names in low-level implementation code. I could have just as easily used PointImpl for Point and selfImpl for _self.