I was playing with shallow copy variations a bit.
Afaik the third example is not doing what I would expect because the compiler creates @[] in the global scope as const seq[T]. But the "copyRef()" helper solves this. If you put example three inside a proc it works as expected.
I think that this "copyRef()" helper is enough to use shallow semantics without much overhead and being clear about what happens.
Still I wonder how to avoid something like in example 3 happening (getting a warning?)
I also wonder why example 2 works in global space too. What is the difference to example 3 in detail here?
Example 1:
var foo1 = @["A","BC","DEF"]
var foo2 = foo1
var foo3 = foo1
foo1[0] = "123"
echo foo1
echo foo2
echo foo3
Output 1:
@[123, BC, DEF]
@[A, BC, DEF]
@[A, BC, DEF]
Example 2:
var bar1 = @["A","BC","DEF"]
bar1.shallow()
var bar2 = bar1
var bar3 = bar1
bar1[0] = "123"
echo bar1
echo bar2
echo bar3
Output 2:
@[123, BC, DEF]
@[123, BC, DEF]
@[123, BC, DEF]
Example 3:
var baz1 = @["A","BC","DEF"]
var baz2: seq[string]
shallowCopy(baz2, baz1)
var baz3 = baz1
baz1[0] = "123"
echo baz1
echo baz2
echo baz3
Output 3:
@[123, BC, DEF]
@[A, BC, DEF]
@[A, BC, DEF]
Example 4:
proc copyRef[T](theSeq: seq[T]): seq[T] =
shallowCopy(result, theSeq)
var baf1 = @["A","BC","DEF"]
var baf2 = baf1.copyRef
var baf3 = baf1
baf1[0] = "123"
echo baf1
echo baf2
echo baf3
Output 4:
@[123, BC, DEF]
@[123, BC, DEF]
@[A, BC, DEF]
@DSblizzard What you likely want is ref seq[T]. This allows you to bypass the deep copy behavior without the limitations of shallow or shallowCopy.
@reactormonk The only way a sequence would be copy-by-reference is if the sequence is either immutable or a double reference. This is because the current sequence type is backed by a dynamically reallocated array. If the array is reallocated, then the address of the array isn't guaranteed to stay the same.
Okay, this is going to be a bit complicated.
First, it's important to understand that most of the time, you won't need shallowCopy at all. Copying is shallow by default if (1) the left-hand side of an assignment is a let variable or (2) the right-hand side is a function call.
This leaves the following two important cases where you may get a deep copy even though you may not want one.
Note that in most cases, the overhead under these circumstances will at most be a constant factor of two (the bulk of the overhead in your earlier code came from using repeated slicing of the same string, which no amount of shallowCopy will protect you against). This is because the string has to be constructed at least once and then a copy of that string is made, after which it can be reused using let assignments.
Thus, the usual caveats against premature optimizations apply; unless profiling tells you that you have a bottleneck due to this, it's probably not worth worrying about.
Also, before using shallowCopy, be aware that it is an unsafe operation (which is why I have an issue open for this). If the right-hand side happens to be a constant, then the operation is not memory-safe. You may consider using swap instead in such cases. Not only is swap memory-safe, it will also avoid aliasing.
That said, in practice, I use the following two templates:
template `<-`*[T](target, source: var T) =
shallowCopy target, source
template alias*[T](varname: untyped, value: var T) =
var varname {.inject.}: type(value)
shallowCopy varname, value
The <- operation is a shallow-assignment operator, and unlike shallowCopy, it is safe (because it requires the right-hand side to be mutable, thus it can't be a constant). The alias is like a var declaration, except that it initializes the variable with a shallow copy of the original; it is basically an alternative to let where you require the variable to be mutable (though it is also more restricted in that the right-hand side has to be an lvalue).