inout params seem to have undefined behavior


(Karl Pickett) #1

I don't believe the (spartan) docs address this case:

func foo(inout a: [Int], inout b: Int) {
    let acopy = a
    a = [4, 5, 6]
    print(acopy) // prints "[1, 2, 3]"
    b = 99
    print(a) // prints "[4, 5, 6]"
    print(acopy) // prints "[1, 2, 99]" (e.g. a let variable changed!)
}

var arr = [1,2,3]

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

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

Is this code "undefined", meaning the spec / doc doesn't specifically
say what should be happening? For instance, I can't believe a let
variable gets changed. The docs also say inout changes the original
value when the function ends, not in the middle as is happening here.


(David Sweeris) #2

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. `b` is then dutifully modified in the function, but there’s no mechanism for copying it back into `arr` when `foo` returns, so the change isn’t reflected at the call site.

I *think* that the documentation you’re referring to in your last sentence is talking about when the value is updated at the call site. That is, `arr` itself isn’t updated until `foo` returns, but within `foo`, the argument `a` would have whatever value you assign it, whenever you assign it.

I think.

HTH (also, I hope I’m right… the only thing worse that no info is bad info)
- Dave Sweeris

···

On Jun 11, 2016, at 12:29 PM, Karl Pickett via swift-users <swift-users@swift.org> wrote:

I don't believe the (spartan) docs address this case:

func foo(inout a: [Int], inout b: Int) {
   let acopy = a
   a = [4, 5, 6]
   print(acopy) // prints "[1, 2, 3]"
   b = 99
   print(a) // prints "[4, 5, 6]"
   print(acopy) // prints "[1, 2, 99]" (e.g. a let variable changed!)
}

var arr = [1,2,3]

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

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

Is this code "undefined", meaning the spec / doc doesn't specifically
say what should be happening? For instance, I can't believe a let
variable gets changed. The docs also say inout changes the original
value when the function ends, not in the middle as is happening here.
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Joe Groff) #3

It's not undefined behavior in that we try to ensure that memory safety is still preserved when inout parameters alias, but it is *unspecified* when updates to an inout parameter will be written back to the original argument. You should avoid aliasing inout parameters for this reason.

-Joe

···

On Jun 11, 2016, at 10:29 AM, Karl Pickett via swift-users <swift-users@swift.org> wrote:

I don't believe the (spartan) docs address this case:

func foo(inout a: [Int], inout b: Int) {
   let acopy = a
   a = [4, 5, 6]
   print(acopy) // prints "[1, 2, 3]"
   b = 99
   print(a) // prints "[4, 5, 6]"
   print(acopy) // prints "[1, 2, 99]" (e.g. a let variable changed!)
}

var arr = [1,2,3]

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

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

Is this code "undefined", meaning the spec / doc doesn't specifically
say what should be happening? For instance, I can't believe a let
variable gets changed. The docs also say inout changes the original
value when the function ends, not in the middle as is happening here.


(Jens Alfke) #4

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

···

On Jun 11, 2016, at 11:57 AM, David Sweeris via swift-users <swift-users@swift.org> wrote:


(David Sweeris) #5

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.

I know. You’d said that you "can't believe a let variable gets changed”. I was just pointing out that you’re correct, in that the compiler will complain if you try to pass one as an in-out argument.

`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 don’t think it can… My recollection is that in Swift the subscript operator (`arr[2]` in this case) can refer to the setter xor the getter, but not both within the same statement. If that’s correct, for there to be a value to pass to the function, `arr[2]` must be referring to the getter version, which means that there’s no setter to update the value when `foo` returns.

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?)

Ah… I see what you mean about a `let` getting modified now… My mistake, I thought you were wondering why `arr` wasn’t `[4, 5, 99]` after foo returned. Yeah, I’m not sure about what’s happening within `foo`... Maybe someone who knows more will come along and provide an explanation, but at the moment I’m inclined to agree — both that you’ve found a bug, and with your guess and to how it’s happening.

- Dave Sweeris

···

On Jun 11, 2016, at 3:36 PM, Jens Alfke <jens@mooseyard.com> wrote:

On Jun 11, 2016, at 11:57 AM, David Sweeris via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:


(Loïc Lecrenier) #6

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


(Jens Alfke) #7

It's not undefined behavior in that we try to ensure that memory safety is still preserved when inout parameters alias, but it is *unspecified* when updates to an inout parameter will be written back to the original argument.

But it seems that memory safety was broken in that an array assigned to a ‘let’ variable was mutated. Doesn’t that violate the contract of its immutability?

Edited version of the example:

  let acopy = a
print(acopy) // prints "[1, 2, 3]"
  b = 99
  print(acopy) // prints "[1, 2, 99]" (e.g. a let variable changed!)

—Jens

···

On Jun 13, 2016, at 9:45 AM, Joe Groff via swift-users <swift-users@swift.org> wrote:


(Brent Royal-Gordon) #8

My recollection is that in Swift the subscript operator (`arr[2]` in this case) can refer to the setter xor the getter, but not both within the same statement.

Quite to the contrary. Rather than using the setter directly, Swift often uses `materializeForSet`, a combined get-and-set operation which is much more efficient, particularly when assigning directly into arrays. To keep from having to use very slow access all the time, it imposes a rule (which is not and cannot be enforced by the compiler) that you can't hold two mutable references to overlapping storage simultaneously, or they may do strange things like lose some of the writes you make.

Here's an old design document discussing some things in this area: <https://github.com/apple/swift/blob/73841a643c087e854a2f62c7e073317bd43af310/docs/proposals/Accessors.rst> I'm not sure how authoritative it is, but it might give you an idea of what's going on.

···

--
Brent Royal-Gordon
Architechies


(David Sweeris) #9

Ack! “Karl”, not you. For some reason I thought you were the OP until right after I clicked send. Sorry for the confusion.

- Dave Sweeris

···

On Jun 11, 2016, at 4:06 PM, David Sweeris via swift-users <swift-users@swift.org> wrote:

On Jun 11, 2016, at 3:36 PM, Jens Alfke <jens@mooseyard.com <mailto:jens@mooseyard.com>> wrote:

On Jun 11, 2016, at 11:57 AM, David Sweeris via swift-users <swift-users@swift.org <mailto: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.

I know. You’d said that you "can't believe a let variable gets changed”. I was just pointing out that you’re correct, in that the compiler will complain if you try to pass one as an in-out argument.

`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 don’t think it can… My recollection is that in Swift the subscript operator (`arr[2]` in this case) can refer to the setter xor the getter, but not both within the same statement. If that’s correct, for there to be a value to pass to the function, `arr[2]` must be referring to the getter version, which means that there’s no setter to update the value when `foo` returns.

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?)

Ah… I see what you mean about a `let` getting modified now… My mistake, I thought you were wondering why `arr` wasn’t `[4, 5, 99]` after foo returned. Yeah, I’m not sure about what’s happening within `foo`... Maybe someone who knows more will come along and provide an explanation, but at the moment I’m inclined to agree — both that you’ve found a bug, and with your guess and to how it’s happening.

- Dave Sweeris
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users


(Karl Pickett) #10

Wow there are some real doozy inout code examples in there, showing
aliasing much more fun than my snippet. Unfortunately I can't
understand anything else the doc is talking about. I guess I'll just
say a prayer and throw salt over my shoulder if using inout.

···

On Sat, Jun 11, 2016 at 6:05 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

My recollection is that in Swift the subscript operator (`arr[2]` in this case) can refer to the setter xor the getter, but not both within the same statement.

Quite to the contrary. Rather than using the setter directly, Swift often uses `materializeForSet`, a combined get-and-set operation which is much more efficient, particularly when assigning directly into arrays. To keep from having to use very slow access all the time, it imposes a rule (which is not and cannot be enforced by the compiler) that you can't hold two mutable references to overlapping storage simultaneously, or they may do strange things like lose some of the writes you make.

Here's an old design document discussing some things in this area: <https://github.com/apple/swift/blob/73841a643c087e854a2f62c7e073317bd43af310/docs/proposals/Accessors.rst> I'm not sure how authoritative it is, but it might give you an idea of what's going on.

--
Brent Royal-Gordon
Architechies


(Loïc Lecrenier) #11

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 :blush:

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


(David Sweeris) #12

Oh, ok, I stand corrected. Thanks for the link :slight_smile:

···

Sent from my iPhone

On Jun 11, 2016, at 18:05, Brent Royal-Gordon <brent@architechies.com> wrote:

My recollection is that in Swift the subscript operator (`arr[2]` in this case) can refer to the setter xor the getter, but not both within the same statement.

Quite to the contrary. Rather than using the setter directly, Swift often uses `materializeForSet`, a combined get-and-set operation which is much more efficient, particularly when assigning directly into arrays. To keep from having to use very slow access all the time, it imposes a rule (which is not and cannot be enforced by the compiler) that you can't hold two mutable references to overlapping storage simultaneously, or they may do strange things like lose some of the writes you make.

Here's an old design document discussing some things in this area: <https://github.com/apple/swift/blob/73841a643c087e854a2f62c7e073317bd43af310/docs/proposals/Accessors.rst> I'm not sure how authoritative it is, but it might give you an idea of what's going on.

--
Brent Royal-Gordon
Architechies


(Joe Groff) #13

I see, missed that part. That's a bug—`acopy` should remain a distinct copy of `a`.

-Joe

···

On Jun 13, 2016, at 1:39 PM, Jens Alfke <jens@mooseyard.com> wrote:

On Jun 13, 2016, at 9:45 AM, Joe Groff via swift-users <swift-users@swift.org> wrote:

It's not undefined behavior in that we try to ensure that memory safety is still preserved when inout parameters alias, but it is *unspecified* when updates to an inout parameter will be written back to the original argument.

But it seems that memory safety was broken in that an array assigned to a ‘let’ variable was mutated. Doesn’t that violate the contract of its immutability?


(Brent Royal-Gordon) #14

Wow there are some real doozy inout code examples in there, showing
aliasing much more fun than my snippet. Unfortunately I can't
understand anything else the doc is talking about. I guess I'll just
say a prayer and throw salt over my shoulder if using inout.

Sorry! Here's the money quote from that document:

···

##### If you didn't catch all that...

That may have been a somewhat intense description, so here's a simple summary of the rule being proposed.

If storage is passed to an inout argument, then any other simultaneous attempt to read or write to that storage, including to the storage containing it, will have unspecified behavior. Reads from it may see partially-updated values, or even values which will change as modifications are made to the original storage; and writes may be clobbered or simply disappear.

But this only applies during the call with the inout argument: the evaluation of other arguments to the call will not be interfered with, and as soon as the call ends, all these modifications will resolve back to a quiescent state.

And this unspecified behavior has limits. The storage may end up with an unexpected value, with only a subset of the writes made to it, and copies from it may unexpectedly reflect modifications made after they were copied. However, the program will otherwise remain in a consistent and uncorrupted state. This means that execution will be able to continue apace as long as these unexpected values don't trip up some higher-level invariant.

--
Brent Royal-Gordon
Architechies


(Zhao Xin) #15

inout says it will copy into the function and copy back after the function
is finished. But the order is unknown. So I think the example,

print(acopy) // prints "[1, 2, 99]" (e.g. a let variable changed!)

i
​s a bug as the value changed inside of the function, before the function
returns.

​Swift doc:


“In-out parameters are passed as follows:

1
​. ​
When the function is called, the value of the argument is copied.
2
​. ​
In the body of the function, the copy is modified.
3
​ .​
When the function returns, the copy’s value is assigned to the original
argument.”

摘录来自: Apple Inc. “The Swift Programming Language (Swift 2.2)”。 iBooks.

Zhaoxin

···

On Sun, Jun 12, 2016 at 9:27 AM, Karl Pickett via swift-users < swift-users@swift.org> wrote:

Wow there are some real doozy inout code examples in there, showing
aliasing much more fun than my snippet. Unfortunately I can't
understand anything else the doc is talking about. I guess I'll just
say a prayer and throw salt over my shoulder if using inout.

On Sat, Jun 11, 2016 at 6:05 PM, Brent Royal-Gordon > <brent@architechies.com> wrote:
>> My recollection is that in Swift the subscript operator (`arr[2]` in
this case) can refer to the setter xor the getter, but not both within the
same statement.
>
> Quite to the contrary. Rather than using the setter directly, Swift
often uses `materializeForSet`, a combined get-and-set operation which is
much more efficient, particularly when assigning directly into arrays. To
keep from having to use very slow access all the time, it imposes a rule
(which is not and cannot be enforced by the compiler) that you can't hold
two mutable references to overlapping storage simultaneously, or they may
do strange things like lose some of the writes you make.
>
> Here's an old design document discussing some things in this area: <
https://github.com/apple/swift/blob/73841a643c087e854a2f62c7e073317bd43af310/docs/proposals/Accessors.rst>
I'm not sure how authoritative it is, but it might give you an idea of
what's going on.
>
> --
> Brent Royal-Gordon
> Architechies
>
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Jens Alfke) #16

I’m guessing that when `a` is copied into `acopy`, the same array contents are shared between them, then when `a` is mutated it gets a new copy of the contents. Then the assignment to `b` writes into the original array contents now held by `acopy`.

Still seems like it’s caused by having aliased `inout` variables, but the effect is nastier. I can imagine horrible bugs where `acopy` gets passed around somewhere else and then changes, breaking the something-else that’s holding it. It could be hard to track that back to its original cause, especially if there are two different codebases involved.

—Jens

···

On Jun 13, 2016, at 1:43 PM, Joe Groff <jgroff@apple.com> wrote:

I see, missed that part. That's a bug—`acopy` should remain a distinct copy of `a`.


(Zhao Xin) #17

According to the latest Swift 3 docs

“As an optimization, when the argument is a value stored at a physical
address in memory, the same memory location is used both inside and outside
the function body. The optimized behavior is known as call by reference; it
satisfies all of the requirements of the copy-in copy-out model while
removing the overhead of copying. Write your code using the model given by
copy-in copy-out, without depending on the call-by-reference optimization,
so that it behaves correctly with or without the optimization.”

摘录来自: Apple Inc. “The Swift Programming Language (Swift 3)”。 iBooks.

​So the behavior is because of the optimization, which should not rely on.

Zhaoxin ​

···

On Tue, Jun 14, 2016 at 5:12 AM, Jens Alfke via swift-users < swift-users@swift.org> wrote:

On Jun 13, 2016, at 1:43 PM, Joe Groff <jgroff@apple.com> wrote:

I see, missed that part. That's a bug—`acopy` should remain a distinct
copy of `a`.

I’m guessing that when `a` is copied into `acopy`, the same array contents
are shared between them, then when `a` is mutated it gets a new copy of the
contents. Then the assignment to `b` writes into the original array
contents now held by `acopy`.

Still seems like it’s caused by having aliased `inout` variables, but the
effect is nastier. I can imagine horrible bugs where `acopy` gets passed
around somewhere else and then changes, breaking the something-else that’s
holding it. It could be hard to track that back to its original cause,
especially if there are two different codebases involved.

—Jens

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