> z <- 1:10
> lobstr::obj_addr(z)
[1] "0x564d8d9eaac8"
> z[[3]] <- 4L
> lobstr::obj_addr(z)
[1] "0x564d882b4328"
Why is the address different after the modifcation? This should be an in place modification, no?
> z <- 1:10
> lobstr::obj_addr(z)
[1] "0x564d8d9eaac8"
> z[[3]] <- 4L
> lobstr::obj_addr(z)
[1] "0x564d882b4328"
Why is the address different after the modifcation? This should be an in place modification, no?
Earlier questions (e.g. here) explain some of the aspects of when R performs a copy. However, there is an additional nuance in OP's example that is worth highlighting here. Note that, the following code has been executed in an R console, not in RStudio. As others have previously pointed out, Rstudio has its own impacts on the process, which we eliminate here by working directly in R.
R's "copy on modify behaviour" can be complicated. R will try to conduct the replacement in place, whenever possible. But, there's an interesting reason that this is not one those situations where it can do so. This is related to the "AltRep" (Alternative Representation) optimizations brought in since R 3.5.0. One such optimization is the ability to store contiguous sequences of numbers using just their end members, rather than allocating the entire vector. See here for more details.
Let's try to see what is going on in this case using .Internal(inspect(z))
to glimpse the internals of the object representation, and tracemem
to detect when an object has been copied:
x = 1L:10L
tracemem(x)
# [1] "<0x559377dba830>"
.Internal(inspect(x))
# @559377dba830 13 INTSXP g0c0 [NAM(7),TR] 1 : 10 (compact)
As we can see, at this stage the 1:10
is represented in the form 1:10 (compact). This means that the full sequence has not yet been explicitly evaluated or allocated. Only a compact representation of its start and end values exists for now..
Now when we assign into an element of the vector, we see that the object does get copied:
x[[3]] = 4L
# tracemem[0x559377dba830 -> 0x559376ae1e48]:
And we can also see that the internals of the object were also changed into an explicit vector of integers rather than the 1:10 "compact" form:
.Internal(inspect(x))
# @559376ae1e48 13 INTSXP g0c4 [NAM(1),TR] (len=10, tl=0) 1,2,4,4,5,...
Now, compare this to the following version in which a copy does not get triggered (because the initial vector was already in its expanded (not compact) form):
x = c(1L:10L)
tracemem(x)
# [1] "<0x55d6146772d8>"
.Internal(inspect(x))
# @55d6146772d8 13 INTSXP g0c4 [NAM(1),TR] (len=10, tl=0) 1,2,3,4,5,...
x[[3]] = 4L
# No copying occured here!!