Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2015-02-03

Parameter passing, part 2 - Immutable value ... only has mutating members named ...

Actually, I wanted to post something different about parameter passing, but since the following just happend to me I thought this would make a nice example of how value and reference passing can sometimes lead to very interesting results.

Such is the case for the "for in .. {}" loop.

The "for in .. {}" loop uses a generator function to retrieve the values from a source. That source can be an array of basic types, or complexer types as structs and classes.

Example:

struct InsertionPoint {
    var index: Int
    var offset: CGFloat
    mutating func adjust(#indexBy: Int, offsetBy: CGFloat) {
        self.index += indexBy
        self.offset += offsetBy
    }
}

var insertionPoints: Array<InsertionPoint> =
    [InsertionPoint(index: 0, offset: 0.0),
     InsertionPoint(index: 10, offset: 120.0)]

let width: CGFloat = 15.0
let chars = 1

for ip in insertionPoints {
    ip.adjust(indexBy: chars, offsetBy: width)

}

I thought that the insertion points in the array will thus be 'adjusted' by the given values.

But no, what I actually got is a compilation error:

ip.adjust(indexBy: chars, offsetBy: width)
         (!)Immutable value of type 'InsertionPoint' only has mutating members named 'adjust' 

At first glance I found this surprising, why should this compilation error occur?

Then I remembered that a struct is passed by value, and the generator function that retrieves the values from the insertionPoints array does exactly that: it gives us value copies from the array. Trying to update these immutable copies simply does not work. Even if it would work, it would be useless (in the above case) since the original values would remain unaffected.

The solution is to work directly with the array itself:

for i in 0 ..< insertionPoints.count {
    insertionPoints[i].adjust(indexBy: chars, offsetBy: width)

}

An array lookup gives us direct access to the value within.

Another solution would be to change the struct to a class. Classes are passed by reference, thus the error would simply disappear:

class InsertionPoint {
    var index: Int
    var offset: CGFloat
    init(index: Int, offset: CGFloat) {
        self.index = index
        self.offset = offset
    }
    func adjust(#indexBy: Int, offsetBy: CGFloat) {
        self.index += indexBy
        self.offset += offsetBy
    }
}

var insertionPoints: Array<InsertionPoint> =
    [InsertionPoint(index: 0, offset: 0.0),
     InsertionPoint(index: 10, offset: 120.0)]

let width: CGFloat = 15.0
let chars = 1

for ip in insertionPoints {
    ip.adjust(indexBy: chars, offsetBy: width)

}

Personally I prefer the later. The code looks cleaner and more readable to me.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

No comments:

Post a Comment