Ответ 1
Краткая версия
Ruby вызывает to_s
, но проверяет, что to_s
возвращает строку. Если это не так, Ruby вызывает стандартную реализацию to_s
. Вызов to_s
рекурсивно не будет хорошей идеей (без гарантии прекращения) - вы могли бы свернуть VM, а код ruby не смог бы сбой всей виртуальной машины.
Вы получаете отличный результат от Fake.new.to_s
, потому что irb вызывает inspect
, чтобы отобразить результат для вас, и inspect
вызывает to_s
второй раз
Длинная версия
Чтобы ответить "что происходит, когда ruby is x", хорошим местом для начала является просмотр того, какие команды генерируются для виртуальной машины (это все зависит от MRI). Для вашего примера:
puts RubyVM::InstructionSequence.compile('"#{Foo.new}"').disasm
выходы
0000 trace 1 ( 1)
0002 getinlinecache 9, <is:0>
0005 getconstant :Foo
0007 setinlinecache <is:0>
0009 opt_send_simple <callinfo!mid:new, argc:0, ARGS_SKIP>
0011 tostring
0012 concatstrings 1
0014 leave
Там некоторые проблемы с кешем, и вы всегда получите trace
, leave
, но в двух словах это говорит.
- получить константу Foo
- вызов его нового метода
- выполнить инструкцию tostring
- выполнить команду concatstrings с результатом инструкции tostring (последнее значение в стеке (если вы делаете это с несколькими последовательностями # {}, вы можете увидеть, как он создает все отдельные строки и затем вызывает concatstrings один раз на всех потребляющих все эти строки)
Инструкции в этом дампе определены в insns.def: это сопоставляет эти инструкции с их реализацией. Вы можете видеть, что tostring
просто вызывает rb_obj_as_string
.
Если вы ищете rb_obj_as_string
через ruby codebase (я нахожу http://rxr.whitequark.org полезный для этого), вы можете увидеть, что он определен здесь как
VALUE
rb_obj_as_string(VALUE obj)
{
VALUE str;
if (RB_TYPE_P(obj, T_STRING)) {
return obj;
}
str = rb_funcall(obj, id_to_s, 0);
if (!RB_TYPE_P(str, T_STRING))
return rb_any_to_s(obj);
if (OBJ_TAINTED(obj)) OBJ_TAINT(str);
return str;
}
Вкратце, если у нас уже есть строка, верните ее. Если нет, вызовите метод to_s
объекта. Затем (и это важно для вашего вопроса), он проверяет тип результата. Если это не строка, она возвращает rb_any_to_s
, а это функция, которая реализует значение по умолчанию to_s