puts すると String じゃないと to_s が呼ばれるが、to_s が String を返さなかったらどうなるか?さらに to_s が呼ばれて無限ループと言うことは無いだろうけど。
class Foo def to_s self end end puts Foo.new #=> #<Foo:0x0000000dcc2858>
Foo#to_s が String じゃないと Object#to_s が呼ばれているのか?
class Object def to_s puts "call Object#to_s" nil end end class Foo def to_s puts "call Foo#to_s" self end end puts Foo.new #=> call Foo#to_s #<Foo:0x0000000ac84948>
どうもそういうわけでもない。ということで、ソースを見てみる。まずは、IO#write を見る。
static VALUE io_write(VALUE io, VALUE str, int nosync) { (中略) str = rb_obj_as_string(str); (中略)
どうも、ここで String に変換しているようだ。
VALUE rb_obj_as_string(VALUE obj) { VALUE str; if (TYPE(obj) == T_STRING) { return obj; } str = rb_funcall(obj, id_to_s, 0); if (TYPE(str) != T_STRING) return rb_any_to_s(obj); if (OBJ_TAINTED(obj)) OBJ_TAINT(str); return str; }
String かどうか調べて、String じゃないとメソッドの to_s を呼んで、その結果が String かどうかまた調べて、String じゃないとこんどはCの関数 rb_any_to_s() を呼んでいる。
VALUE rb_any_to_s(VALUE obj) { const char *cname = rb_obj_classname(obj); VALUE str; str = rb_sprintf("#<%s:%p>", cname, (void*)obj); OBJ_INFECT(str, obj); return str; }
と、ここで有無を言わせず文字列に変換している。