Strange values written in loop

I don't understand what's happening when I save values via a loop. I initialize an array with default values, then run a loop to assign calculated values to it. In the middle of the loop, I print values, then print values again after the loop is over. The array values sometimes change, even though nothing has been written between print calls (when they change, the values are equal the last value in the array, index 49).

I made a test file which writes four types of values to an array: (1) A new class instance, (2) Calculation, (3) Variable, (4) Hard-code. Saving the same value gives different results between the different write methods:

import Foundation

let numElements : Int = 50
class CustomType{
    var x : Double
    var y : Double
    init(x: Double = 1.23, y: Double = 2.34) {
        self.x = x
        self.y = y
    }
}

// Try this four different ways
var array1 = [CustomType](repeating:CustomType(), count:numElements)
var array2 = [CustomType](repeating:CustomType(), count:numElements)
var array3 = [CustomType](repeating:CustomType(), count:numElements)
var array4 = [CustomType](repeating:CustomType(), count:numElements)
// Checking that defaults were written
print("Pre: Point 1: (\(array1[44].x),\(array1[44].y))")
print("Pre: Point 2: (\(array2[44].x),\(array2[44].y))")
print("Pre: Point 3: (\(array3[44].x),\(array3[44].y))")
print("Pre: Point 4: (\(array4[44].x),\(array4[44].y))")

// --- Fix 1: Problem goes away if I uncomment this:
// array1[44]=CustomType()
// array2[44]=CustomType()
// array3[44]=CustomType()
// array4[44]=CustomType()
// --- Fix 2: Or if you swap these two lines for the following line:
// let index = 44
// do {
for index in 0..<numElements{
    let rads = Double(index) * 2 * Double.pi/Double(numElements)
    let sinrads = sin(rads), cosrads = cos(rads)
    // Four different ways to save to arrays
    array1[index] = CustomType(x:sin(rads),y:cos(rads))
    array2[index].x = sin(rads)
    array2[index].y = cos(rads)
    array3[index].x = sinrads
    array3[index].y = cosrads
    array4[index].x = -0.684547105928689
    array4[index].y =  0.7289686274214113
    
    if(index==44){
        print("\n== Printing results mid-loop at index 44 ==")
        print("During: index: \(index), Calculated Rads:   \(rads)")
        print("During: Calculated Vals:   (\(sin(rads)),\(cos(rads)))")
        print("During: Stored 'let' Vals: (\(sinrads),\(cosrads))")
        print("During: Point 1: (\(array1[44].x),\(array1[44].y))")
        print("During: Point 2: (\(array2[44].x),\(array2[44].y))")
        print("During: Point 3: (\(array3[44].x),\(array3[44].y))")
        print("During: Point 4: (\(array4[44].x),\(array4[44].y))")
    }
}

print("\n== Printing the same results after the loop ==")
print("Post: Point 1: (\(array1[44].x),\(array1[44].y))")
print("Post: Point 2: (\(array2[44].x),\(array2[44].y))")
print("Post: Point 3: (\(array3[44].x),\(array3[44].y))")
print("Post: Point 4: (\(array4[44].x),\(array4[44].y))")
print("\n== Reverse-calculating results from a correct array (array 1) to get the for loop index ==")
print("reverse index calculation 01: \( (atan2(array1[ 1].x,array1[ 1].y) + Double.pi * 0) * Double(numElements)/(2*Double.pi) )")
print("reverse index calculation 44: \( (atan2(array1[44].x,array1[44].y) + Double.pi * 2) * Double(numElements)/(2*Double.pi) )")
print("reverse index calculation 45: \( (atan2(array1[45].x,array1[45].y) + Double.pi * 2) * Double(numElements)/(2*Double.pi) )")
print("\n== Reverse-calculating results from an incorrect array (array 2) to get the for loop index ==")
print("reverse index calculation  1: \( (atan2(array2[ 1].x,array2[ 1].y) + Double.pi * 2) * Double(numElements)/(2*Double.pi) )")
print("reverse index calculation 44: \( (atan2(array2[44].x,array2[44].y) + Double.pi * 2) * Double(numElements)/(2*Double.pi) )")
print("reverse index calculation 45: \( (atan2(array2[45].x,array2[45].y) + Double.pi * 2) * Double(numElements)/(2*Double.pi) )")

Which gives the following output:

Pre: Point 1: (1.23,2.34)
Pre: Point 2: (1.23,2.34)
Pre: Point 3: (1.23,2.34)
Pre: Point 4: (1.23,2.34)

== Printing results mid-loop at index 44 ==
During: index: 44, Calculated Rads:   5.529203070318036
During: Calculated Vals:   (-0.684547105928689,0.7289686274214113)
During: Stored 'let' Vals: (-0.684547105928689,0.7289686274214113)
During: Point 1: (-0.684547105928689,0.7289686274214113)
During: Point 2: (-0.684547105928689,0.7289686274214113)
During: Point 3: (-0.684547105928689,0.7289686274214113)
During: Point 4: (-0.684547105928689,0.7289686274214113)

== Printing the same results after the loop ==
Post: Point 1: (-0.684547105928689,0.7289686274214113)
Post: Point 2: (-0.12533323356430465,0.9921147013144778)
Post: Point 3: (-0.12533323356430465,0.9921147013144778)
Post: Point 4: (-0.684547105928689,0.7289686274214113)

== Reverse-calculating results from a correct array (array 1) to get the for loop index ==
reverse index calculation 01: 1.0000000000000002
reverse index calculation 44: 43.99999999999999
reverse index calculation 45: 45.0

== Reverse-calculating results from an incorrect array (array 2) to get the for loop index ==
reverse index calculation  1: 49.0
reverse index calculation 44: 49.0
reverse index calculation 45: 49.0
Program ended with exit code: 0

Re-initializing the objects prior to the loop fixes the problem (see "Fix 1" in the comments), but the elements of the array are all initialized during creation and I don't understand why doing it a second time is helpful. The values should all be the same, am I missing something simple?

Answered by endecotp in 816072022

Try changing the class to a struct.

Accepted Answer

Try changing the class to a struct.

The structs aren't modified while being read, shouldn't a class and a struct behave the same in this situation?

No. I think the problem occurs earlier than you think.

In Swift, structs have mutable value semantics and classes have reference semantics. What happens in your code is that:

var array2 = [CustomType](repeating:CustomType(), count:numElements)

This has created a single instance of CustomType which every element of array2 now refers to.

You can probably work out the rest. When you set e.g. array2[index].x = sin(rads) you have updated the x property of that single object, which you'll now see when you read from array2[another_index]. Call it "spooky action at a distance". On the other hand, when you do array1[index] = CustomType(....) you have created a new instance that is not shared.

This is one of those things that is almost cultural and gets embedded in how developers think. In languages like Java that have primarily reference semantics it all seems totally natural once you've been doing it for a few years. But if you're used to value-semantic languages then this is something you'll constantly be tripping over if you have to move to the "other side".

Generally, use structs for everything. Only use a class if you have a very good reason to do so.

Strange values written in loop
 
 
Q