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

33,546

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.

Share:
33,546
Jeff Storey
Author by

Jeff Storey

Updated on February 28, 2020

Comments

  • Jeff Storey
    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 vs to_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 define to_str? Can you give a practical application for this method?

  • Jeff Storey
    Jeff Storey about 12 years
    Andrew, 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
    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 of to_s and to_str do just that: return themselves. Implementing to_str is a way of validating that you act like a String. Many core methods use to_str in this way. The blog post I quoted from is worth reading :).
  • Jeff Storey
    Jeff Storey about 12 years
    Andrew, 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
    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
    Alex D about 12 years
    Jeff, 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
    Jeff Storey about 12 years
    Andrew, that's a really good example with the numerics. That helps to clarify.
  • ybakos
    ybakos almost 12 years
    Take a look at the Ruby koans about_to_str example for some more insight.
  • thomthom
    thomthom almost 10 years
    Is there similar for Floats?
  • Andrew Marshall
    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
    ahnbizcad over 9 years
    If 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 using to_str
  • ahnbizcad
    ahnbizcad over 9 years
    does symbol have short and long versions of conversion coercion methods?
  • Andrew Marshall
    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.