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

RubyStringMethods

Ruby Problem Overview


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?

Ruby Solutions


Solution 1 - Ruby

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 - Ruby

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.

Solution 3 - Ruby

Zverok has a great easily understandable article about when to use what (explained with to_h and to_hash).

It has to do whether your Object implementing those methods can be converted to a string -> use to_s
or it is a type of some (enhanced) string -> use to_str


I've seen a meaningful usage of to_hash in practice for the Configuration class in the gem 'configuration' (GitHub and Configuration.rb)

It represents -- as the name says -- the provided configuration, which in fact is a kind of hash (with additional features), rather than being convertible to one.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJeff StoreyView Question on Stackoverflow
Solution 1 - RubyAndrew MarshallView Answer on Stackoverflow
Solution 2 - RubyEldritch ConundrumView Answer on Stackoverflow
Solution 3 - RubydCSevenView Answer on Stackoverflow