Reference versus copy

I create a simple MWE to demonstrate some kind of reference problem which is leading to unwanted behavior when creating matrices with function elements. Here is the code:

function test()
    zfcts = Any[t -> 0., t -> 0.]
    fcts = Any[t -> sin(t), t -> cos(t)]
    g = Any[]
    push!(g, zfcts)
    push!(g, zfcts)
    g[1][1] = fcts[1]  # sin
    g[2][1] = fcts[2]  # cos
    x = 2.1
    @show g[1][1](x)
    @show (g[2][1](x))
    @show (g[1] === g[2])  # Should  not be true!!!  <<<<<<
    @show (g[1][1] === g[2][1])  # true
    @show (g[1][2] === g[2][2])  # true
    @show (g[1][1] === g[1][2])  # true
    @show (fcts[1] === fcts[2])  # false (as it should be)
end

test()

I expect

g[1][1] = sin(2.1)
g[2][[1] = cos(2.1)

However, the results are always the same, and equal to fcts[2], the second function define in the fcts list. I thought that push! did a copy.

Here is the code output:

> test()
((g[1])[1])(x) = -0.5048461045998576
((g[2])[1])(x) = -0.5048461045998576
g[1] === g[2] = true
(g[1])[1] === (g[2])[1] = true
(g[1])[2] === (g[2])[2] = true
(g[1])[1] === (g[1])[2] = false
fcts[1] === fcts[2] = false
false

As usual, any insight is appreciated. I feel as if I should understand this coming from a C++ background, but I clearly am missing something important about functions and push!. Thanks.

Your title implies you know exactly what’s going on here?

g is a vector containing two references to the same vector. When you change the first element of that vector, it changes, no matter which reference you use to access it.

My title indicates that I “should” know what is going on.
I just solve the problem (thanks for the push):

push!(g, copy(zfcts))
push!(g, copy(zfcts))

The rest of the code is the same.

Where did you read that push! copies? I can’t find that in the documentation. I would try it, but it appears you already have.

Does push!(g, copy(zfcts)) work?

1 Like

Yes, simply copying the list of functions inside the push! did the trick. Regarding operation of the push, I just assumed. I do not check every little detail in the documentation. I guess it does do a copy: what was copied was the pointer to the beginning of the list of functions (using C++ lingo).

Passing arguments in Julia (as for most languages) works just like assignment (=): it binds a
new name to the same object, it does not copy the object. This not a little detail — it is fundamental to how the language works.

(Of course, a function like push! could internally call copy on its argument before storing it in a data structure, but this is the exception rather than the rule.)

(Nor is Julia’s behavior particularly unusual among languages here.)

4 Likes