Sorry for the terse answer, I’ll try to expand a bit on my reasoning here.

In the Swift book, in “Language Reference” -> “Declarations” -> “In-Out 
Parameters”, it says:
“You can’t pass the same argument to multiple in-out parameters because the 
order in which the copies are written back is not well defined”.

Now, I am not 100% sure whether &arr and &arr[2] can be considered the same 
argument, but I would argue that they can because arr contains arr[2].
And because passing the same argument to two inout parameters is not allowed, 
the compiler can use an optimization like call-by-reference.

Here is an example, where the compiler assumes the arguments are not the same, 
and therefore uses call-by-reference instead of copy-in-copy-out:
struct S {
    var a: Int
}
func foo(inout s: S, _ a: inout Int) {
    a += 1
    s.a += 1
}

var s = S(a: 0)
foo(&s, &s.a)
print(s) // prints 2

So, to come back to the original example. Here is what I think is happening.
Even though arrays are value types, internally they use a reference-counted 
buffer. In order to mutate the array, the internal buffer must be uniquely 
referenced. If it is not, a new identical buffer is created.

So I’m going to follow the life of these internal buffers in the sample code:

var arr = [1,2,3]
// arr.buffer = buffer1 (new buffer)
// buffer1’s reference count: +1

foo(&arr, b: &arr[2])

func foo(inout a: [Int], inout b: Int) {
    // buffer1: +1
    
    let acopy = a
    // acopy.buffer = a.buffer (which is buffer1)
    // buffer1: +2
    
    a = [4, 5, 6]
    // a changes, but it has value semantics and a.buffer’s reference count is 
> 1
    // Therefore a new buffer is created.
    // a.buffer = buffer2 (new buffer identical to buffer1)
    // Now:
    // buffer1: +1
    // buffer2: +1
    
    print(acopy)  // prints buffer1: "[1, 2, 3]"

    b = 99  // b points to address in buffer1 because of call-by-reference 
optimization
    // buffer1[2] = 99

    print(a)      // prints buffer2: "[4, 5, 6]"
    print(acopy)  // prints buffer1: "[1, 2, 99]"

    // Now a is returned -> buffer2 is returned and stays alive
    // acopy not returned -> buffer1’s reference count drops to zero -> it is 
destroyed
}

print(arr)  // prints buffer2: "[4, 5, 6]"

I hope this helps and that I haven’t made any mistake 😊

Loïc


> On Jun 11, 2016, at 10:52 PM, Loïc Lecrenier via swift-users 
> <swift-users@swift.org> wrote:
> 
> Hi,
> 
> I think what you said is correct. However, it is not a bug. We can't pass two 
> inout arguments that alias each other because then the behaviour is 
> undefined. It is documented in the Swift book somewhere. 
> 
> Loïc
> 
> Sent from my iPad
> 
> On Jun 11, 2016, at 10:36 PM, Jens Alfke via swift-users 
> <swift-users@swift.org> wrote:
> 
>> 
>>> On Jun 11, 2016, at 11:57 AM, David Sweeris via swift-users 
>>> <swift-users@swift.org> wrote:
>>> 
>>> You can’t pass a `let` as an `inout` argument. I’d guess that’s what’s 
>>> happening is the `arr[2]` part is creating a temporary var to which the `&` 
>>> part then provides a reference.
>> 
>> But `arr` is a var, not a let.
>> 
>>> `b` is then dutifully modified in the function, but there’s no mechanism 
>>> for copying it back into `arr` when `foo` returns
>> 
>> No, it gets copied back using subscript assignment. Remember, `inout` isn’t 
>> really passing the address of the parameter (although the optimizer may 
>> reduce it to that.) It’s literally in-and-out: the caller passes the 
>> original value, the function returns the new value, the caller then stores 
>> the new value where the old value came from.
>> 
>> I am not a Swift guru, but I think the problem in this example is that 
>> there’s a sort of race condition in that last post-return stage: the 
>> function has returned new values for both `arr` and arr[2]`, both of which 
>> get stored back where they came from, but the ordering is significant 
>> because arr[2] will have a different value depending on which of those 
>> assignments happens first.
>> 
>> This smells like those C bugs where the result of an expression depends on 
>> the order in which subexpressions are evaluated — something like “x = i + 
>> (i++)”. The C standard formally declares this as undefined behavior.
>> 
>> The part I’m still confused by is how `acopy` got modified within the `foo` 
>> function, since it’s declared as `let`. After staring at this for a while 
>> longer, I’m forced to conclude that the compiler decided it could optimize 
>> the `b` parameter by actually passing a pointer to the Int and modifying it 
>> directly, and that this has the side effect of modifying the Array object 
>> that `acopy` is pointing to, even though it’s supposed to be immutable.
>> 
>> In other words, this looks like a compiler bug. I can reproduce it with 
>> Swift 2.2 (which is what my `swift` CLI tool says it is, even though I have 
>> Xcode 7.3.1 and I thought that was Swift 2.3?)
>> 
>> —Jens
>> _______________________________________________
>> swift-users mailing list
>> swift-users@swift.org
>> https://lists.swift.org/mailman/listinfo/swift-users
> _______________________________________________
> swift-users mailing list
> swift-users@swift.org
> https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

Reply via email to