`push!` semantics

It doesn’t seem to me the behavior of push!, as seen in the program below, is clear from the documentation. Should I file an issue about it, or is this just my confusion?

Specifically, I wasn’t sure whether the Vector got a copy of the object pushed, or the object itself. The semantics have tripped up others, and that question by @gideonsimpson has an answer by @kristoffer.carlsson. The answer is that the Vector will contain the original object. Of course, one can push an explicit copy() if that’s the goal.

I came to this wondering about the behavior of objects that are sent through a Channel; I suspect the story is the same there since, at least when buffered, Channel uses a Vector internally (julia 1.9.2).

No doubt part of the confusion stems from interference from knowledge about C++, which is all over handling this kind of stuff explicitly. Since julia has no pointers, references (well, not sure what Ref() does), copy constructors, in-place update … it’s a much different world.

I’m also a bit disturbed because I thought Vector was roughly giving me a continguous block of bits which would allow more efficient processing. At least for structs that seems not to be the case.

mutable struct TBag
    a::Int16
    b::Int16
end

function main()
    v = Vector{TBag}()
    b = TBag(1, 44)
    push!(v, b)  # does v get a copy of b, or b itself?
    b.a = 99
    println(v[1].a)  # 1 or 99?
end
main()

99 is the result.

the answer here is that Julia uses “pass by sharing” which means that mutable objects are always passed by reference (immutables let the compiler choose since you can’t observe a difference)

1 Like

Julia does have pointers, references, copy constructors, and in-place update.

julia> v = collect(1:5)
5-element Vector{Int64}:
 1
 2
 3
 4
 5

julia> pointer(v)
Ptr{Int64} @0x00007f7a2b56aa40

julia> unsafe_load(ans, 5)
5

julia> Ref(v, 3)
Base.RefArray{Int64, Vector{Int64}, Nothing}([1, 2, 3, 4, 5], 3, nothing)

julia> unsafe_store!(pointer(v), 9, 3)
Ptr{Int64} @0x00007f7a2b56aa40

julia> Ref(v, 3)[]
9

@Oscar_Smith if calls were pass by value then it would be impossible for a called function, push!() in this case, to refer to the original object. Since it is pass by sharing, it can refer to the original value. But I don’t think knowing that alone is enough to know what it does with the value. If my mental model of a Vector as a continuous chunk of TBag structures had been correct (it isn’t), it would have to be a different object that it stored, albeit one with the same property values. Or, the function could have always made a copy on its own.

Putting it more directly, it seems to me push! could behave in either way–using the original or keeping its own copy–and be consistent with the documentation, i.e., the documentation is ambiguous.

@mkitti Thanks for the correction. Would one ever use any of those constructs except for programming on the language?

I don’t know who changed the title of this topic to “does push! copy?” or marked it solved, but I wish you hadn’t. I changed it back. Since my initial question stated that push! did not copy, I didn’t make the post to find the answer to that question. Nor, for the reasons given above, do I think the first response answers the question about whether it copies. Again, my question was " Should I file an issue about it, or is this just my confusion?". One possible response would be pointing to something in the documentation that does resolve the issue.

2 Likes

I think the basic answer is that in Julia, essentially no API ever does an implicit copy and we do not currently have any mechanism that allows precise enough control of memory placement that some of the fancier mechanism from other languages would be required in Julia (that doesn’t mean that Julia is necessarily slower of course - the defaults are quite well optimized). As a result, it’s not really clear where this piece of documentation should go. push! isn’t really the right place, because from a julia user’s perspective, there’s no reason to expect it to do anything other than store the reference it has in the vector, because that is always julia semantics. Perhaps the best place to put this would be in the noteworthy differences section: Noteworthy Differences from other Languages · The Julia Language

5 Likes

In my experience, it should do no harm to duplicate information in such case because as a newcomer he/she will either go to push! or Noteworthy Differences from other Languages · The Julia Language.