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