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
andto_s
] are not particularly strict: if an object has some kind of decent representation as a string, for example, it will probably have ato_s
method… [to_int
andto_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.