Ruby: to_s vs. to_str
I've experienced a bit of confusion when it comes to Ruby's to_s vs. to_str methods. This site says:
to_str is an implicit cast, whereas to_s is an explicit cast. ... If you?re having a hard time remembering which is which, I would remember that there is a reason that to_s is shorter. First, it implies that the object isn?t really much of a string, so we?re only using the first letter ?s?. Also, to_s is shorter because more objects will have to_s methods, so you?ll end up typing it more frequently. With to_str, we?re tagging an object as much closer to being a string, so we give it the first three letters. It?s almost half of a string!
That's about as vague as you can get. It's also a bit confusing because although to_s is supposedly an "explicit class", some built-in methods call it implicitly (e.g. puts).
Programming Ruby, in the Duck Typing chapter (page 372) says:
[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 you object can naturally be used every place a string or an integer could be used.
That's more helpful, but the notion of "strict" vs. "not very strict" certainly isn't very precise. For any given built-in method, does it use to_s or to_str? That's what I'd like to know.
I wrote a test:
# to_s vs. to_str test
module String_Test
def initialize(arg)
@test = arg
end
def to_s
puts 'to_s called!'
'to_s ' + @test
end
def to_str
puts 'to_str called!'
'to_str ' + @test
end
end
class Both
include String_Test
end
class No_to_s
include String_Test
undef_method :to_s
end
class No_to_str
include String_Test
undef_method :to_str
end
class Neither
include String_Test
undef_method :to_s
undef_method :to_str
end
[Both, No_to_s, No_to_str, Neither].each do |curr_class|
puts
puts '=' * 40
puts curr_class.to_s
puts '=' * 40
t = curr_class.new('foo')
[
%q{t },
%q{p t },
%q{print t, "\n" },
%q{puts t },
%q{puts t.to_s },
%q{puts t, t },
%q{"#{t}" },
%q{puts "#{t}" },
%q{puts t.to_str },
%q{puts 'ADDING:' + t },
%q{'123'.split(t) },
%q{Dir.glob(t) },
%q{File.new(t) },
%q{/./.match(t) },
%q{nil.respond_to?(t) },
].each do |code|
puts 'RUNNING: ' + code
begin
eval code
rescue Exception => e
puts "ERROR: #{e}"
end
puts '-' * 20
end
end
The output (which are long and I won't paste here) shows that:
print,putsand string interpolation useto_s. Mostly everything else (glob,split,match, string concatenation) usesto_str.- Defining
to_strwill NOT defineto_sfor you, and vice versa. Going by the vague intuitive descriptions given above, I'd almost have expected methods that useto_sto know enough to fall back to usingto_str. If an object is inherently in some sense a String, why would print/puts output anything BUT the String interpretation of the object? If we're including thisto_s/to_strconvention in the language, you'd think that an implicitto_s->to_strfallback would be there too. respond_to?fails. This is because it expects a Symbol. Strings have ato_symmethod, but my class doesn't. So even thoughto_strmeans your class "can naturally be used every place a string can be used", my class obviously is NOT really a string, since it doesn't inherit all of Strings methods. So there are plenty of places a String "can be used" that my class cannot, for various definitions of "can be used".
So I imagine if you're writing your own class which takes arguments that you plan to coerce into Strings, you should use to_s only if you're outputting or String-interpolating a value, and use to_str if you're doing anything substantial. But this really is a bit vague for my liking.

5 Comments
I would look at it this way: to_s is a tool to output human readable status/debug info to_str is expected to be program readable.
Heck, just make one an alias for the other.
It's naive to define to_s as being human readable. That would imply it has to output the value in the appropriate locale, but 3000.to_s doesn't correctly insert a comma!
to_sis defined on every object and will always return something.to_stris only defined on objects that are string-like. For example,Symbolhasto_strbutArraydoes not.Thus, you can use
obj.respond_to?(:to_str)instead of something likeobj.is_a?(String)if you want to take advantage of duck typing without worrying about whether the class you're working with is a subclass ofStringor not.The documentation for
Exception#to_strreads: "By supplying a to_str method, exceptions are agreeing to be used where Strings are expected." So it's all about expectations.to_s: Give me aStringno matter what!to_str: I am pretty sure you areString-like. So sure, that I'd prefer to get aNoMethodExceptionin the case where you're not.looks like string interpolation "falls back" on #inspect ?
Speak your Mind
Preview