There's actually two copies of 2222
here. The first is the literal "2222"
, which is stored in the program binary and not freed until the OS cleans up the program; the other has a more complicated journey. It starts on this line, which makes a copy of the literal, places it on the heap in a new allocation, and stores a pointer to that heap allocation within the String
structure on the stack:
let mut new_str: String = String::from("2222");
You then make a mutable reference to the String
, which in turn still contains a pointer to the heap allocation and pass that into do_something
:
do_something(s, &mut new_str);
This, in turn takes a different String
value (pointing to a different heap allocation) and overwrites the original String
:
*new_str = s;
Immediately before the write actually happens, the original String
value is dropped because it is no longer reachable— &mut
references guarantee that there is no other way to access the value that they point at.
String
doesn't have a Drop
implementation itself, but contains a Vec<u8>
which actually manages the heap allocation. This gets dropped as part of the process of dropping the String
, and Vec
does implement Drop
, which drops all of the elements in the vector, but doesn't yet free the heap memory. Vec
contains a field of type RawVec
which gets dropped after Vec::drop()
is run, and it's RawVec
's Drop
implementation that actually frees the heap memory that contains 2222
.
The memory for the String
struct itself is reused to hold the newly-assigned String
, and will be dropped when the variable new_str
leaves scope at the end of main
, freeing the memory for 1111
by a similar process.
For a more detailed explanation of how all this works under the hood, the Nomicon contains a chapter describing how Vec
works in detail. From a user's perspective, however, you just need to remember that assigning to a place that's already occupied will drop whatever was there before the assignment, as @nerditation says.