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(s, &mut new_str);
This, in turn takes a different
String value (pointing to a different heap allocation) and overwrites the original
*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
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
Drop implementation that actually frees the heap memory that contains
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.