The below code doesn't crash, but I can't reason why, given the "limited" docs available.
func foo(inout a: [Int], inout b: Int) {
a = []
b = 99
}
var arr = [1,2,3]
// confusion: where does the "out" of b go to?
// storage for a was already cleared / invalidated
foo(&arr, b: &arr[2])
print(arr) // arr is empty
I believe this is what is happening. When you assign
a = []
you are pointing a
to an new array. The original array still exists in memory and when you do:
b = 99
you are modifying the original array, not the new array that a
references.
What evidence do you have that this is the case?
Consider this modification to your experiment:
Case 1:
func foo(inout a: [Int], inout b: Int) {
a[0] = 4
a[1] = 5
a[2] = 6
b = 99
}
var arr = [1,2,3]
foo(&arr, b: &arr[2])
print(arr) // prints "[4, 5, 99]"
Now consider this one:
Case 2:
func foo(inout a: [Int], inout b: Int) {
a = [4, 5, 6]
b = 99
}
var arr = [1,2,3]
foo(&arr, b: &arr[2])
print(arr) // prints "[4, 5, 6]"
Clearly, modifying the individual elements of a
is not the same as assigning an array to a
.
In Case 1, we modified the original array turning the elements into 4
, 5
, and 6
, and the assignment of b
changed a[2]
as expected.
In Case 2, we assigned [4, 5, 6]
to a
which didn't change the original values to 4
, 5
, and 6
but instead pointed a
to a new array. The assignment of b
does not change a[2]
in this case because a
now points to a new array at a different location in memory.
Case 3:
func foo(inout a: [Int], inout b: Int) {
let a1 = a
a = [4, 5, 6]
b = 99
print(a) // prints "[4, 5, 6]"
print(a1) // prints "[1, 2, 99]"
}
var arr = [1,2,3]
foo(&arr, b: &arr[2])
print(arr) // prints "[4, 5, 6]"
In Case 3, we are able to assign the original array to a1
before assigning a new array to a
. This gives us a name for the original array. When b
is assigned, a1[2]
gets modified.
From the comments:
Your answer explains why the assignment to b inside the function works. However, when foo ends and copies the inout variables back, at that point I don't see how swift knows to defer deallocating the original array until after the &[2] assignment.
It is likely that this is a result of reference counting by ARC. The original array is passed by reference to foo
and the reference count is incremented. The original array isn't deallocated until the reference count is decremented at the end of foo
.
It seems just as hairy as what the docs already disallow - passing the same variable twice as inout. Also your case3 is surprising. Shouldn't the let a1 = a line do struct / value semantics and copy a snapshot of the array right then?
Yes. I agree that Case 3 is surprising, but it does reveal some of what is going on under the covers. Normally when you assign one array to a new variable, Swift does not immediately make a copy. Instead, it just points the second array to the first and the reference count incremented. This is done for efficiency sake. It is only necessary to make a copy when one of the arrays is modified. In this case, when a
gets modified, a1
keeps the original copy of the array in memory.
That's really confusing; I don't see why a1 wouldn't get 1,2,3. Also a let should be immutable!
The fact that a1
gets modified when b
is set shows that a1
is pointing to the memory of the original array. Swift apparently doesn't consider setting b
as modifying a1
. Perhaps because it already made sure a
was mutable when foo
was called with &a[2]
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With