在ruby中使用字符串插值会发生什么?

时间:2014-08-25 15:05:40

标签: ruby

我认为ruby只是调用方法to_s但我无法解释它是如何工作的:

class Fake
  def to_s
    self
  end
end

"#{Fake.new}"

根据逻辑,由于无穷大递归,这应该会使堆栈级别过高。但它工作正常,似乎从一个对象调用#to_s。

=> "#<Fake:0x137029f8>"

但为什么?

增加:

class Fake
  def to_s
    Fake2.new
  end
end

class Fake2
  def to_s
    "Fake2#to_s"
  end
end

此代码在两种情况下的工作方式不同:

puts "#{Fake.new}" => "#<Fake:0x137d5ac4>"

可是:

puts Fake.new.to_s => "Fake2#to_s"

我认为这是不正常的。有人可以建议在ruby解释器中它发生在内部吗?

1 个答案:

答案 0 :(得分:11)

短版

Ruby确实调用to_s,但它会检查to_s是否返回一个字符串。如果没有,ruby会调用to_s的默认实现。以递归方式调用to_s不是一个好主意(不保证终止) - 您可能会崩溃VM并且ruby代码不应该崩溃整个VM。

您获得Fake.new.to_s的不同输出,因为irb调用inspect向您显示结果,inspect第二次调用to_s

长版

要回答“当ruby执行x时会发生什么”,一个好的起点是查看为VM生成的指令(这是所有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      

缓存存在一些问题,你总是得到traceleave,但简而言之,这就是说。

  1. 得到常数Foo
  2. 调用其新方法
  3. 执行tostring指令
  4. 使用tostring指令的结果执行concatstrings指令(堆栈上的最后一个值(如果使用多个#{}序列执行此操作,您可以看到它构建所有单个字符串,然后在所有消耗时调用concatstrings一次所有这些字符串)
  5. 此转储中的说明在insns.def中定义:这会将这些说明映射到其实现中。您可以看到tostring只是致电rb_obj_as_string

    如果您通过ruby代码库搜索rb_obj_as_string(我发现http://rxr.whitequark.org对此有用),您可以将其定义为here

    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的函数