to_s vs. to_str (and to_i/to_a/to_h vs. to_int/to_ary/to_hash) in Ruby

Solution 1:

Note first that all of this applies to each pair of “short” (e.g. to_s/to_i/to_a/to_h) vs. “long” (e.g. to_str/to_int/to_ary/to_hash) coercion methods in Ruby (for their respective types) as they all have the same semantics.


They have different meanings. You should not implement to_str unless your object acts like a string, rather than just being representable by a string. The only core class that implements to_str is String itself.

From Programming Ruby (quoted from this blog post, which is worth reading all of):

[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 [your] object can naturally be used every place a string or an integer could be used.

Older Ruby documentation from the Pickaxe has this to say:

Unlike to_s, which is supported by almost all classes, to_str is normally implemented only by those classes that act like strings.

For example, in addition to Integer, both Float & Numeric implement to_int (to_i's equivalent of to_str) because both of them can readily substituted for an Integer (they are all actually numbers). Unless your class has a similarly tight relationship with String, you should not implement to_str.

Solution 2:

To understand if you should use/implement to_s/to_str, let's look at some exemples. It is revealing to consider when these method fail.

1.to_s              # returns "1"
Object.new.to_s     # returns "#<Object:0x4932990>"
1.to_str            # raises NoMethodError
Object.new.to_str   # raises NoMethodError

As we can see, to_s is happy to turn any object into a string. On the other hand, to_str raises an error when its parameter does not look like a string.


Now let us look at Array#join.

[1,2].join(',')     # returns "1,2"
[1,2].join(3)       # fails, the argument does not look like a valid separator.

It is useful that Array#join converts to string the items in the array (whatever they really are) before joining them, so Array#join calls to_s on them.

However, the separator is supposed to be a string -- someone calling [1,2].join(3) is likely to be making a mistake. This is why Array#join calls to_str on the separator.


The same principle seems to hold for the other methods. Consider to_a/to_ary on a hash:

{1,2}.to_a      # returns [[1, 2]], an array that describes the hash
{1,2}.to_ary    # fails, because a hash is not really an array.

In summary, here is how I see it:

  • call to_s to get a string that describes the object.
  • call to_str to verify that an object really acts like a string.
  • implement to_s when you can build a string that describes your object.
  • implement to_str when your object can fully behave like a string.

I think a case when you could implement to_str yourself is maybe a ColoredString class -- a string that has a color attached to it. If it seems clear to you that passing a colored comma to join is not a mistake and should result in "1,2" (even though that string would not be colored), then do implement to_str on ColoredString.