Does splatting create a shallow copy?

A while ago I asked a question about shallow and deep copies and why a change in the copied array doesn’t reflect on the original.

When you splat an array, does it create a shallow or deep copy?

As explained in this answer, because it’s a 1 dimensional array, does it copy the first level?

letters = String["a","b","c","d","e"]
copy_of_letters = String[letters...]
copy_of_letters[3] = "x"

Output:

copy_of_letters
5-element Array{String,1}:
 "a"
 "b"
 "x"
 "d"
 "e"

letters
5-element Array{String,1}:
 "a"
 "b"
 "c"
 "d"
 "e"

“shallow” I guess, because splatting only splat the depth-1:

julia> a = [[1], [2]]
2-element Vector{Vector{Int64}}:
 [1]
 [2]

julia> b = [a...]
2-element Vector{Vector{Int64}}:
 [1]
 [2]

julia> b[1][1] = 10; b
2-element Vector{Vector{Int64}}:
 [10]
 [2]

julia> a
2-element Vector{Vector{Int64}}:
 [10]
 [2]
1 Like

Well, what would be the difference between shallow and deep in your case? Strings are immutable, so you do not have how to “change inside a String” that may (or may not) be in both arrays to test if it is modified in both.

If you test with a mutable object you can see a shallow copy:

julia> refs = [Ref(1), Ref(2), Ref(3)]
3-element Array{Base.RefValue{Int64},1}:
 Base.RefValue{Int64}(1)
 Base.RefValue{Int64}(2)
 Base.RefValue{Int64}(3)

julia> copy_of_refs = Base.RefValue{Int64}[refs...]
3-element Array{Base.RefValue{Int64},1}:
 Base.RefValue{Int64}(1)
 Base.RefValue{Int64}(2)
 Base.RefValue{Int64}(3)

julia> refs[2][] = 20
20

julia> refs
3-element Array{Base.RefValue{Int64},1}:
 Base.RefValue{Int64}(1)
 Base.RefValue{Int64}(20)
 Base.RefValue{Int64}(3)

julia> copy_of_refs
3-element Array{Base.RefValue{Int64},1}:
 Base.RefValue{Int64}(1)
 Base.RefValue{Int64}(20)
 Base.RefValue{Int64}(3)
1 Like

Does the documentation exist for Base.RefValue? I would like to read about it. Typing ? into the REPL and Base.RefValue returns No documentation found.

You’re looking for

help?> Ref
search: Ref WeakRef prevfloat UndefRefError GlobalRef uppercasefirst lowercasefirst ProcessFailedException searchsortedfirst

  Ref{T}

  An object that safely references data of type T. This type is guaranteed to point to valid, Julia-allocated memory of
  the correct type. The underlying data is protected from freeing by the garbage collector as long as the Ref itself is
  referenced.

  In Julia, Ref objects are dereferenced (loaded or stored) with [].

  Creation of a Ref to a value x of type T is usually written Ref(x). Additionally, for creating interior pointers to
  containers (such as Array or Ptr), it can be written Ref(a, i) for creating a reference to the i-th element of a.

  When passed as a ccall argument (either as a Ptr or Ref type), a Ref object will be converted to a native pointer to the
  data it references.

  There is no invalid (NULL) Ref in Julia, but a C_NULL instance of Ptr can be passed to a ccall Ref argument.

  Use in broadcasting
  ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡

  Ref is sometimes used in broadcasting in order to treat the referenced values as a scalar:

  julia> isa.(Ref([1,2,3]), [Array, Dict, Int])
  3-element BitArray{1}:
   1
   0
   0
1 Like

Bare with me here cause I’m still learning :grinning:. I want to make sure I have a good understanding of copy() before I start using it.

When you have a simple structure, for example, an array or multidimensional array, copy() will copy the structure and elements inside making them independent, meaning any changes done to the copy won’t reflect in the original, correct?

Example:

even_numbers = Int32[2,4,6,8,10] # Creating a 5 element array.
b = copy(even_numbers)
deleteat!(b, 2) # the element of 4 should be removed.
println(even_numbers)

Output:

5-element Array{Int32,1}:
  2
  4
  6
  8
 10

However, if the structure is more complicated for example, nested arrays or dictionaries, it will copy the structure, and copy references to the original elements, meaning any change done to the copy, will reflect in the original structure, correct?

copy copies 1-level, deepcopy is recursive

1 Like

A wise decision.

In practice things happens as you describe, but there is also a fundamental misunderstanding here: copy is blind to the inner elements, it does not take different decisions based on the type of the inner elements, it always do the same thing: copying a reference to the element.

“But what about Ints”, you may say, “they are copied by value” or yet, “changes I make to them do not reflect in the original array”. To that I have two answers:

  1. “changes I make to them do not reflect in the original array” – You never make changes to an Ints object, they are immutable. You cannot take 5 and modify it. You can have an Int field/position and modify which Int object is inside it, but the field/position itself is a property of the container not of the object; the object itself is never modified. This confusion arises of the old: binding/label/reference versus variable/box/memory-block.
  2. “they are copied by value” – They may, or they may not. This is just a compiler optimization. And this is not just for Ints but for any immutable object, if the compiler deems the object “too large to be copied” or is able to perceive it will be copied to a lot of places, it can instead copy a reference instead. As the object is immutable, it is impossible to the user to see a semantic difference: the user cannot change the object to see a change in many places. You can sometimes see a performance difference however: see this very interesting case in which the compiler is able to do an unexpectedly smart deduction.

I think we are at the binding/label/reference versus variable/box/memory-block again. If you can forget memory layout for a moment, the best way to see it is that copy over a container always duplicate the container itself but the container is always a container of bindings/labels/references. Consequently, if you copy