Search code examples
swiftclassstructlazy-initializationmutating-function

Initialization problem with Swift struct that contains lazy initializers


I wrote two versions of code for a Swift programming exercise from an online programming exercise website. The exercise is as follows:

Find the difference between the square of the sum and the sum of the squares of the first N natural numbers. The square of the sum of the first ten natural numbers is (1 + 2 + ... + 10)² = 55² = 3025. The sum of the squares of the first ten natural numbers is 1² + 2² + ... + 10² = 385. Hence the difference between the square of the sum of the first ten natural numbers and the sum of the squares of the first ten natural numbers is 3025 - 385 = 2640.

The code for the first version is:

struct Squares {
    let squareOfSum : UInt64
    let sumOfSquares : UInt64
    let differenceOfSquares : UInt64

    init(_ number: UInt64) {
        var sum = ((1 + number) * number) / 2
        squareOfSum = sum * sum
        sum = 0
        for i in 1...number {
            sum = sum + (UInt64)(i * i)
        }
        sumOfSquares = sum
        differenceOfSquares = squareOfSum - sumOfSquares
    }
}
let sqrs = Squares(5)
print(sqrs.differenceOfSquares)

It passes compilation and runs without problem.

The code for the second version is:

struct Squares {
    let num : UInt64
    lazy var squareOfSum: UInt64 = {
        return ((1...num).reduce(0, +)).squared
    }()
    lazy var sumOfSquares: UInt64 = {
        return ((1...num).map{$0.squared}).reduce(0, +)
    }()
    lazy var differenceOfSquares: UInt64 = {return squareOfSum - sumOfSquares }()

    init(_ number: UInt64) {
        num = number
    }
}
extension Numeric {
    var squared: Self {
        return self * self
    }
}
let sqrs = Squares(5)
print(sqrs.differenceOfSquares)

When the code is compiled, the compiler produces the following messages:

/tmp/0C9C2EC3-543D-486F-BCB0-D181EC48DC82.8NCVch/main.swift:25:7: error: cannot use mutating getter on immutable value: 'sqrs' is a 'let' constant
print(sqrs.differenceOfSquares)
      ^~~~
/tmp/0C9C2EC3-543D-486F-BCB0-D181EC48DC82.8NCVch/main.swift:24:1: note: change 'let' to 'var' to make it mutable
let sqrs = Squares(5)
^~~
var

If I change let into var for sqrs, as suggested by the compiler, the code will run well. Alternatively, if I change the type struct into class to make the object mutable, the code will also run well. However, in the code in my first version, there's not problem with the combination of a struct object and a let mutability delimiter. How come in the second version, these two become incongruent? While I don't know why, I suspect that it may have to do with the lazy initializers in the second version.

Can anyone please help explain this? Thank you very much in advance.


Solution

  • Lazy properties have mutating getters, i.e. accessing a lazy property will potentially mutate the struct. Why? Think about the first time you access a lazy property. Before the first access, that property has no value, after you access it, the expression on the right of = is evaluated and assigned to the property. Now the property has a value. This is how accessing a lazy property can change the struct. As a result, you need a var to access lazy properties.

    Changing the struct into a class also helps, because now sqrs stores a reference, and let sqrs only makes the reference to the object constant, rather than making the object itself constant.

    The first version initialises all the properties in the initialiser. You are allowed to assign to each let property once in the initialiser, which is what the first version did. And since the properties are not lazy in the first version, you can access them with a let constant.