When will the original value be changed with the copied value?

I find out that the following code will change the value of solution to [1.1, 1.1, 1.1, 1.1], although I don’t really want to change its value. It seems that the value of solution is changing with the value of solution_temp.

solution = ones(4)
step_size = 0.1
for i = 1:length(solution)
    solution_temp = solution
    solution_temp[i] = solution_temp[i] + step_size
end

On the other hand, this will not change the value of solution with the value of solution_temp.

solution_temp = solution
solution_temp = solution_temp .+ step_size

I wonder if there is a general rule to know when the first case will happen and when the second case will happen? Or how should I avoid the first case?

solution_temp = solution is not creating a copy, solution_temp and solution will be the exact same object. Because they’re the same object, editing one will change the “other” (because it’s not actually “other”). The behavior you wanted is probably solution_temp = copy(solution), so that solution_temp is now a different object from (but a copy of) solution.
solution_temp .+ step_size is creating a new object, which you are then assigning to solution_temp.

3 Likes

That happened because solution is a mutable object (an Array). cf. https://docs.julialang.org/en/v1/manual/types/#Mutable-Composite-Types-1

In short: solution_temp = solution means “make that solution_temp contains the same value as solution (not a copy)”. So, as Elrod said, changing the content of solution_temp also changes that of solution. With immutable types that does not happen, just because you cannot change immutable objects, only replace them by other.

To know when there is risk of this happening, you can use isimmutable:

julia> isimmutable(solution)
false

Thanks for your explanation, @Elrod and @heliosdrm.

So the following code will change the value of solution

solution = ones(4)
solution_temp = solution
solution_temp .= 2

However, this will not

solution = ones(4)
solution_temp = solution
solution_temp = [2.,2.,2.,2.]

I wonder what is the difference between these two cases. It seems that if I change the value of solution_temp one element by one element, it will also change the value of solution. If I change it all together, it will not change the value of solution. I am really confused.

You can think of arrays (and mutable structs) as containers, like buckets.
Here:

solution = ones(4)
solution_temp = solution

You create one bucket of 4 ones. Both solution and solution_temp refer to that bucket.

solution_temp .= 2

You now replace every element of that bucket with a 2.
Versus:

solution_temp = [2.,2.,2.,2.]

You create a new bucket filled with 2s, and call it solution_temp.

You can try using ===

help?> ===
search: === == !==

  ===(x,y) -> Bool
  ≡(x,y) -> Bool


  Determine whether x and y are identical, in the sense that no program could distinguish them. First the types of x and y are compared. If those are identical, mutable objects are compared
  by address in memory and immutable objects (such as numbers) are compared by contents at the bit level. This function is sometimes called "egal". It always returns a Bool value.

  Examples
  ≡≡≡≡≡≡≡≡≡≡

  julia> a = [1, 2]; b = [1, 2];

  julia> a == b
  true

  julia> a === b
  false

  julia> a === a
  true

After you do

solution_temp = solution

then solution_temp === solution (they’re the same bucket). But, [2.,2.,2.,2.] or copy(solution) create new ones, so that it would be false.

julia> [2.,2.,2.,2.] === [2.,2.,2.,2.]
false

julia> twos = [2.,2.,2.,2.]
4-element Array{Float64,1}:
 2.0
 2.0
 2.0
 2.0

julia> twos === twos
true

julia> twos === [2.,2.,2.,2.]
false

julia> twos == [2.,2.,2.,2.]
true
2 Likes

I did not find the specific section of the manual that explains this, so I will try to explain it myself (but I am almost sure such section exists).

The problem I see is that your mental model on how variables and objects work is not how they work in Julia (but I am sure in other languages you would see the behavior you did expect).

In Julia, a variable is just a name for some value in some scope. When you attribute a value to some variable you are never copying it, you are binding it, this is: just giving it a name so it can be referred later (and is not collected by garbage collection in the meantime). In C/C++ and other languages, variables are specific places in memory, and when you attribute something to a variable you are always copying something, be it the value you are interested or just a pointer to it. In julia making x = y is no different than saying, “oh, this thing I am calling y right now, well, now x is another name for it”. So, when in your second example you do:

solution = ones(4)
solution_temp = solution
solution_temp = [2.,2.,2.,2.]

It can be read as:

  1. solution is the name for the return of ones(4);
  2. solution_temp is also a name for the same thing;
  3. nevermind, solution_temp is instead a name for this brand new vector or twos;
2 Likes

It is briefly mentioned here:

https://docs.julialang.org/en/v1/manual/noteworthy-differences

  • Julia arrays are not copied when assigned to another variable. After A = B , changing elements of B will modify A as well.
  • Julia values are not copied when passed to a function. If a function modifies an array, the changes will be visible in the caller.
1 Like

This FAQ item touches on this question (but from a different angle):

https://docs.julialang.org/en/v1/manual/faq/#I-passed-an-argument-x-to-a-function,-modified-it-inside-that-function,-but-on-the-outside,-the-variable-x-is-still-unchanged.-Why?-1

Several blog posts and topics here deal with the question, but maybe a specific FAQ item could be useful. It is really a consequence of language semantics: Julia here is doing the simplest thing possible. But this may be confusing to people used to other semantics.

1 Like

Hi, thank you all for your help. I really appreciate it.