I’ve been working on a codebase that relied on storing a lot of objects in R environments, mainly because of the potential speed improvements with large numbers of objects.
See this article for a pretty good explanation.
After a recent spec change, I needed to start looping around a block of code that was previously using a single environment to store objects. The easiest approach was to create an initial base environment to use at the start of each iteration of the loop, and then create a copy of that environment that would be specific to the loop iteration.
But I got a surprise!
All of the result environments looked like the last iteration.
First, let’s look at how this works when you start with a base list, make a copy, and modify the copy:
> original <- list() > copy <- original
copy hasn’t been modified, it shares the same memory
original, for efficiency:
tracemem(original)  "<0x4c5d738>" > tracemem(copy)  "<0x4c5d738>"
So let’s modify
copy, and notice that it’s now copied to its own memory address:
> copy$item <- 1L tracemem[0x4c5d738 -> 0x5a4c1b8]:
And if we now compare
copy, we get completely expected differences:
> original list() > copy $item  1 > original$item NULL > identical(original, copy)  FALSE
Now let’s repeat with environments:
> original <- new.env() > copy <- original
And if we use
tracemem as before, we get a hint that something is different:
> tracemem(original) Error in tracemem(original) : 'tracemem' is not useful for promise and environment objects
So if we simply print the environments, we’ll see the memory addresses:
> original <environment: 0x69d6cc8> > copy <environment: 0x69d6cc8> > identical(original, copy)  TRUE
Now let’s modify
copy, and notice that the memory address doesn’t change:
> copy$item <- 1L > original <environment: 0x69d6cc8> > copy <environment: 0x69d6cc8>
Presumably that means that
original also now has an element named
> copy$item  1 > original$item  1 > identical(original, copy)  TRUE
So if we remove the element
original, it should also disappear from
> rm('item', pos = as.environment(original)) > original$item NULL > names(original) character(0) > copy$item NULL > identical(original, copy)  TRUE
So, if you’ve ever copied an existing environment thinking you could modify it independently of the original, you should revisit that code to make sure it’s not causing problems!