rubystringinterpolation

How does Ruby the String interpolation of a String object?


Is my assumption correct that Ruby does not call the to_s or to_str method when interpreting a String object during string-interpolation?

a = "Hallo"
class String
  def to_s
    "Servus"
  end
end
def a.to_s
  "Servus"
end
puts "Hier sagt man #{a}"

=> Hier sagt man Hallo

Is it more efficient to check if an object is a String than to simply call the to_s method?


Solution

  • Is my assumption correct that Ruby does not call the to_s or to_str method when interpreting a String object during string-interpolation?

    It is correct.

    The test you provided doesn't rule out the possibility that the parser took a shortcut by recognizing that a was assigned to a string literal. Here's a test that does:

    a = "Hallo"
    class String
      def to_s
        "Servus"
      end
    end
    def a.to_s
      "Servus"
    end
    
    test_string = 'puts "Hier sagt man #{' + 97.chr + '}'
    
    eval(test_string) # => Hier sagt man Hallo
    

    Now, Ruby doesn't actually do any kind of "local variable is a literal string" automatic substitution, because Ruby is so dynamic that the value of a could be updated in any of the following lines of code without the parser knowing. But if you didn't already know for sure that that's the case, a test like the above guarantees it.


    Is it more efficient to check if an object is a String than to simply call the to_s method?

    Here's another interesting test, which shows that Ruby (at least C Ruby) isn't using some secret shortcut to check if it's exactly a String, but is rather using something more like is_a?:

    class Foo < String
      def to_s
        "foo"
      end
    
      def to_str
        "foo"
      end
    end
    
    a = Foo.new("initial value")
    
    puts "a: #{a}"
    
    #=> a: initial value
    

    If we had seen a: foo instead, we would know that a very simple (and presumably more efficient) check was used, and was unable to recognize that the Foo object was an instance of a String subclass.

    Given the above, we've ruled out the possibility that the test for String is a simple test optimized for exactly String classes. Without examining the code in every Ruby implementation, there a few other things we know for sure:

    1. Ruby method lookups are a source of inefficiency.
    2. It only takes a single is_a? method call to identify whether an object is an instance of a String subclass.
    3. Ruby implementations vary, but C Ruby at least may not even have to do a method lookup for is_a?, because it can just use rb_obj_is_kind_of for any object type.
    4. Checking for to_s and to_str causes at least 1 or 2 method lookups. Actually calling them causes an unknown number of method lookups and additional calls.
    5. As noted in other comments, there's no guarantee that to_s or to_str actually returns a string, so the is_a? check has to be repeated.

    Considering these things, we can say that it's at least twice as efficient to check if the object is a String as to call to_s or to_str without checking first, and on average it's likely much more efficient.