In general1, the .= operator just assigns into whatever the result of the left hand side evaluates to — and in this case the result is a brand new array with the contents of x and y vertically concatenated. You can see that [x; y] creates a new array decoupled from x and y by just trying it by itself:
x = zeros(2)
y = zeros(3)
r = [x;y]
r[1] = 1
julia> r
5-element Array{Float64,1}:
1.0
0.0
0.0
0.0
0.0
julia> x
2-element Array{Float64,1}:
0.0
0.0
julia> y
3-element Array{Float64,1}:
0.0
0.0
0.0
julia> r .= [1.2, 4.5, 2.3, 4.5, 5.6] # just changes `r`, not `x` or `y`
5-element Array{Float64,1}:
1.2
4.5
2.3
4.5
5.6
julia> all(iszero, x) && all(iszero, y)
true
Now, you can update x and y if they're put into a special "lazy" container from LazyArrays.jl that emulates a concatenation operation:
julia> using LazyArrays
julia> ApplyArray(vcat, x, y) .= [1.2, 4.5, 2.3, 4.5, 5.6]
5-element ApplyArray{Float64,1,typeof(vcat),Tuple{Array{Float64,1},Array{Float64,1}}}:
1.2
4.5
2.3
4.5
5.6
julia> x
2-element Array{Float64,1}:
1.2
4.5
julia> y
3-element Array{Float64,1}:
2.3
4.5
5.6
1 There's one important exception to this general rule: we support indexed assignment with multiple selected indices in combination with .= to update the original array. In other words, the syntax y[1:2] .= [3.4, 5.6] will indeed update the first two elements of y, even though y[1:2] elsewhere will allocate a brand new 2-element array decoupled from y. In other words, when you use indexing on the left-hand side of .=, it automatically uses a view when necessary.