Ruby: to_s vs. to_str

I've experienced a bit of confusion when it comes to Ruby's to_s vs. to_str methods. This site says:

to_str is an implicit cast, whereas to_s is an explicit cast.
...
If you’re having a hard time remembering which is which, I would remember that there is a reason that to_s is shorter. First, it implies that the object isn’t really much of a string, so we’re only using the first letter ’s’. Also, to_s is shorter because more objects will have to_s methods, so you’ll end up typing it more frequently.

With to_str, we’re tagging an object as much closer to being a string, so we give it the first three letters. It’s almost half of a string!

That's about as vague as you can get. It's also a bit confusing because although to_s is supposedly an "explicit class", some built-in methods call it implicitly (e.g. puts).

Programming Ruby, in the Duck Typing chapter (page 372) says:

[to_i and to_s] are not particularly strict: if an object has some kind of decent representation as a string, for example, it will probably have a to_s method...

[to_int and to_str] are strict conversion functions: you implement them only if you object can naturally be used every place a string or an integer could be used.

That's more helpful, but the notion of "strict" vs. "not very strict" certainly isn't very precise. For any given built-in method, does it use to_s or to_str? That's what I'd like to know.

I wrote a test:

# to_s vs. to_str test
 
module String_Test
    def initialize(arg)
        @test = arg
    end
 
    def to_s
        puts 'to_s called!'
        'to_s ' + @test
    end
 
    def to_str
        puts 'to_str called!'
        'to_str ' + @test
    end
end
 
class Both
    include String_Test
end
 
class No_to_s
    include String_Test
    undef_method :to_s
end
 
class No_to_str
    include String_Test
    undef_method :to_str
end
 
class Neither
    include String_Test
    undef_method :to_s
    undef_method :to_str
end
 
[Both, No_to_s, No_to_str, Neither].each do |curr_class|
    puts
    puts '=' * 40
    puts curr_class.to_s
    puts '=' * 40
 
    t = curr_class.new('foo')
    [
        %q{t                       },
        %q{p t                     },
        %q{print t, "\n"           },
        %q{puts t                  },
        %q{puts t.to_s             },
        %q{puts t, t               },
        %q{"#{t}"                  },
        %q{puts "#{t}"             },
        %q{puts t.to_str           },
        %q{puts 'ADDING:' + t      },
        %q{'123'.split(t)          },
        %q{Dir.glob(t)             },
        %q{File.new(t)             },
        %q{/./.match(t)            },
        %q{nil.respond_to?(t)      },
 
    ].each do |code|
        puts 'RUNNING: ' + code
        begin
            eval code
        rescue Exception => e
            puts "ERROR: #{e}"
        end
        puts '-' * 20
    end
end

The output (which are long and I won't paste here) shows that:

  • print, puts and string interpolation use "to_s". Mostly everything else (glob, split, match, string concatenation) uses to_str.
  • Defining to_str will NOT define to_s for you, and vice versa. Going by the vague intuitive descriptions given above, I'd almost have expected methods that use to_s to know enough to fall back to using to_str. If an object is inherently in some sense a String, why would print/puts output anything BUT the String interpretation of the object? If we're including this to_s / to_str convention in the language, you'd think that an implicit to_s -> to_str fallback would be there too.
  • respond_to? fails. This is because it expects a Symbol. Strings have a to_sym method, but my class doesn't. So even though to_str means your class "can naturally be used every place a string can be used", my class obviously is NOT really a string, since it doesn't inherit all of Strings methods. So there are plenty of places a String "can be used" that my class cannot, for various definitions of "can be used".

So I imagine if you're writing your own class which takes arguments that you plan to coerce into Strings, you should use to_s only if you're outputting or String-interpolating a value, and use to_str if you're doing anything substantial. But this really is a bit vague for my liking.

Tags:

One Response to “Ruby: to_s vs. to_str”

  1. Quoth Samuel:

    I would look at it this way:
    to_s is a tool to output human readable status/debug info
    to_str is expected to be program readable.

Leave a Reply

You can use these tags in comments (Note: HTML is automatically escaped inside <pre> tags, nowhere else, so if you post source code, put it in <pre>):

<pre lang="some_programming_language"> 
<em>
<strong>
<a href="url">

NOTE: Comments are automatically spam-filtered. If your comment fails to appear, it was likely munched by the filter. Try not to link-spam or post anything that looks like it was typed by a robot.