This is because the string implementation of your version of ruby ββfor substr has a special case for preserving memory allocations when you take substr, which is the tail of the original string, and the length of the string is long enough not to store the string value in the structure of the base object.
If you trace the code, you will see that the index of the range string[0...100] will go through this section in rb_str_substr . Thus, a new line will be highlighted through str_new3 , which allocates a new object structure (therefore object_id is different), but sets the string value of the ptr field as a pointer to the extended storage of the original object and sets the ELTS_SHARED flag to indicate that the new object ELTS_SHARED storage with another object.
In your code, you take this new substring object and assign it to the var @string , which is still a live link when you start garbage collection. Since there is a live link to the dedicated storage of the source string, it cannot be compiled.
In the ruby ββtrunk, this optimization for sharing storage on compatible tail substrings seems to still exist.
The other two vars unused_variable_2 and unused_variable_3 do not have this extended repository sharing problem, because they are installed using mechanisms that provide different repository, so they collect garbage, as expected, when their links disappear from the scope.
Line # dup runs rb_str_replace (via initialize_copy binding ), which replaces the contents of the original string with a copy of the contents of the original string and ensures that the storage is not used.
String # new (source_str) goes through rb_str_init , which likewise provides excellent storage with rb_str_replace at the original initial value.
source share