To_s in classes as default for its objects

In Ruby if you provide a to_s method in your classes it will be default method to convert its objects to strings like below.

class Myclass
  def to_s ... end

exmpl =
puts "put something here #{exmpl}"

In Crystal I have to do this with my class to_s to get what I want.

puts "put something here #{exmpl.to_s}"

I’ve read the docs, but don’t see how to not write #{exmpl.to_s} and just write #{exmpl}.

You want to override to_s(io : IO) then write the string content to that io.

1 Like

Could you give an example. I couldn’t get it to work.

def to_s(io : IO)
  io << "My class"

Thanks. (Better docs explanations and examples needed.)

(Better docs explanations and examples needed.)

I read a lot of complaints against the documentation lately, but personally I found it quite useful and pleasant to browse (even though I have no Ruby knowledge).

In this particular case, here is an excerpt from the String page in the reference manual:

Interpolation is implemented using a String::Builder and invoking Object#to_s(IO) on each expression enclosed by #{…}.
The expression “sum: #{a} + #{b} = #{a + b}” is equivalent to: do |io|
  io << "sum: "
  io << a
  io << " + "
  io << b
  io << " = "
  io << a + b

And it’s also mentioned here:

In Crystal, puts will invoke to_s(io) on the object, passing it the IO to which the string representation should be written.

When writing custom types, always be sure to override to_s(io) , not to_s , and avoid creating intermediate strings in that method.

Followed by an extensive example.


Where would you expect that documentation to be? What’s necessary to make it better?

As @elbywan already said, this is mentioned in several places in the language reference. It’s also explained in the API docs for Object#to_s.

1 Like

Look, coming from Ruby I know if you provide a to_s method in your classes it’s used when doing string interpolation of object, in: puts " ...#{obj}"

I was trying to figure out (quickly) how to get this behavior in Crystal.

I originally did it like this, just to test the working of my code (not worrying about pretty output).

class XYZ
  def to_s()
    "(#{a} #{sgn(b)}i #{sgn(c)}j #{sgn(d)}k)\n" 

  private def sgn(n)  
    n.sign|1 == 1 ? "+ #{n}" : "- #{n.abs}"  

This forced me to explicitly write .to_s inside: puts " ..."#{xyz.to_s}".
Knowing now how to do it below I can just do: puts " ..."#{xyz}", like I wanted.

class XYZ
  def to_s(io : IO) 
    io << "(#{a} #{sgn(b)}i #{sgn(c)}j #{sgn(d)}k)\n" 

  private def sgn(n)  
    n.sign|1 == 1 ? "+ #{n}" : "- #{n.abs}"  

I initially went here here and put to_s in search bar, which gave me a bunch of choices, and ultimately went here.

This did not show me how to use the method to do what I wanted, or what to do to do what I wanted. So instead (again) of spending too much time trying to figure where to find this in the docs, I simply asked the question here, and got the answer I needed.

I said (as an aside) better documentation is needed, because there were no immediate examples to show me how to do what I wanted (where I looked at).

Documentation for users (and developers) should answer at least 3 questions:

  • how to use a method, or do something
  • why you should/could do this
  • when you should/could do this

I’m not going to rehash here all my reasons and pleadings to improve documentation. See here

The comment was to provide feedback that I tried to use the docs to answer the question before posting it, and didn’t find the answer I needed elsewhere.

Aww, the method overloading trap (didn’t have the required types in the parameter, to_s() vs to_s(io : IO)). I was in the same boat and that’s why I view them as a negative. I feel the pain

Probably could add a better example of overloading #to_s(io : IO) to That probably is the missing piece.

PRs are welcome :wink:


If anything, it should go in Object#to_s(io)

The documentation for String#to_s (which is just copied from Object#to_s) should actually guide in the right direction. It can be improved and written more informative, but it points to #to_s(io : IO).

This points to the need for good cross-referencing, which to do efficiently requires a dedicated automated documentation system.