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.
Jeff Storey
Updated on February 28, 2020Comments
-
Jeff Storey over 4 years
I'm learning Ruby and I've seen a couple of methods that are confusing me a bit, particularly
to_s
vsto_str
(and similarly,to_i
/to_int
,to_a
/to_ary
, &to_h
/to_hash
). What I've read explains that the shorter form (e.g.to_s
) are for explicit conversions while the longer form are for implicit conversions.I don't really understand how
to_str
would actually be used. Would something other than a String ever defineto_str
? Can you give a practical application for this method? -
Jeff Storey about 12 yearsAndrew, thanks. But in a practical sense, what does that mean "those classes that act like strings?" Does that mean they implement the same methods as Strings? And what else would to_str return other than self if the object already acts like a string?
-
Andrew Marshall about 12 years@JeffStorey Yes. Unless your class can be substituted for/by a String, you should not implement
to_str
. String's implementation ofto_s
andto_str
do just that: return themselves. Implementingto_str
is a way of validating that you act like a String. Many core methods useto_str
in this way. The blog post I quoted from is worth reading:)
. -
Jeff Storey about 12 yearsAndrew, thanks. I did read that post and I was still a little unclear. Is there ever a time where you might implement to_str? That's the part I'm really confused about...I can't think of a practical example offhand.
-
Andrew Marshall about 12 years@JeffStorey Unless you're creating a class that's just like a String and implements at a minimum all the methods it does, no. Of course in this case you're likely to have subclasses String anyway, and get it for free. A good example of when to do so is Ruby's numerics: Float, Integer, & Numeric all implement
to_int
(to_i
's "to_str
" equivalent), because they can be substituted for each other readily. -
Alex D about 12 yearsJeff, in theory, you could implement a
to_str
method which returned an actual String. That would make your class usable everywhere where a String is; in any case where a String was needed,to_str
would be invoked, and the resulting String used. So you could mix instances of your custom class together with Strings and work with them homogenously. Would you really want to do that? It's completely up to you! Ruby is a language which gives the programmer a lot of flexibility to "bend" the language any way you want. YOU decide how best to use that flexibility. -
Jeff Storey about 12 yearsAndrew, that's a really good example with the numerics. That helps to clarify.
-
ybakos almost 12 yearsTake a look at the Ruby koans about_to_str example for some more insight.
-
thomthom almost 10 yearsIs there similar for Floats?
-
Andrew Marshall almost 10 years@thomthom Nope:
Float.instance_methods.grep(/to_/) #=> [:to_c, :to_enum, :to_f, :to_i, :to_int, :to_r, :to_s]
. -
ahnbizcad over 9 yearsIf you can only use
to_str
when it acts like a string, then what is the point of using it? I may have missed the explanation, but maybe the explanation needs to be more explicit in the purpose/utility of usingto_str
-
ahnbizcad over 9 yearsdoes symbol have short and long versions of conversion coercion methods?
-
Andrew Marshall over 9 years@gwho No, there’s not (there’s just
to_sym
, which his arguably the “long” version since it’s only on String & Symbol). Probably because Symbol is really special, and you likely want to coerce-to/act-like a String instead. But I’m just guessing there.