I thought that ruby just call method to_s but I can't explain how this works:
class Fake
def to_s
self
end
end
"#{Fake.new}"
By the logic this should raise stack level too deep because of infinity recursion. But it works fine and seems to call #to_s from an Object.
=> "#<Fake:0x137029f8>"
But why?
ADDED:
class Fake
def to_s
Fake2.new
end
end
class Fake2
def to_s
"Fake2#to_s"
end
end
This code works differently in two cases:
puts "#{Fake.new}" => "#<Fake:0x137d5ac4>"
But:
puts Fake.new.to_s => "Fake2#to_s"
I think it's abnormal. Can somebody suggest when in ruby interpreter it happens internally?
String Interpolation refers to substitution of defined variables or expressions in a given String with respected values. This is how, string Interpolation works, it executes whatever that is executable. Let's see how to execute numbers and strings. Syntax: #{variable}
String interpolation is a technique that enables you to insert expression values into literal strings. It is also known as variable substitution, variable interpolation, or variable expansion. It is a process of evaluating string literals containing one or more placeholders that get replaced by corresponding values.
Interpolation is a method of constructing new data points within range of discrete set of known data points. The number of data points obtained by sampling or experimentation represents values of function for limited number of values of independent variable.
Instead of terminating the string and using the + operator, you enclose the variable with the #{} syntax. This syntax tells Ruby to evaluate the expression and inject it into the string. This is the same program you've already written, but this time we're using string interpolation to create the output.
Ruby does call to_s
, but it checks that to_s
returns a string. If it doesn't, ruby calls the default implementation of to_s
instead. Calling to_s
recursively wouldn't be a good idea (no guarantee of termination) - you could crash the VM and ruby code shouldn't be able to crash the whole VM.
You get different output from Fake.new.to_s
because irb calls inspect
to display the result to you, and inspect
calls to_s
a second time
To answer "what happens when ruby does x", a good place to start is to look at what instructions get generated for the VM (this is all MRI specific). For your example:
puts RubyVM::InstructionSequence.compile('"#{Foo.new}"').disasm
outputs
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
There's some messing around with the cache, and you'll always get trace
, leave
but in a nutshell this says.
The instructions in this dump are defined in insns.def: this maps these instructions to their implementation. You can see that tostring
just calls rb_obj_as_string
.
If you search for rb_obj_as_string
through the ruby codebase (I find http://rxr.whitequark.org useful for this) you can see it's defined here as
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;
}
In brief, if we already have a string then return that. If not, call the object's to_s
method. Then, (and this is what is crucial for your question), it checks the type of the result. If it's not a string it returns rb_any_to_s
instead, which is the function that implements the default to_s
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With