Ruby“定义?”运算符错误?

所以,我们有代码:

class Foo def bar puts "Before existent: #{(defined? some_variable)}" puts "Before not_existent: #{(defined? nonexistent_variable)}" raise "error" some_variable = 42 rescue puts "exception" ensure puts "Ensure existent: #{(defined? some_variable)}" puts "Ensure not_existent: #{(defined? nonexistent_variable)}" end end 

并从irb调用它:

 > Foo.new.bar 

而且,这将返回:

 Before existent: Before not_existent: exception Ensure existent: local-variable Ensure not_existent: => nil 

现在问题 – 为什么? 我们之前提出了exception,而不是some_variable 。 为什么这样工作? 为什么在ensure块中定义some_variable ? (顺便说一下,它定义为零)

更新:感谢@Max的回答,但如果我们更改代码以使用实例变量:

 class Foo def bar puts "Before existent: #{(defined? @some_variable)}" puts "Before not_existent: #{(defined? @nonexistent_variable)}" raise "error" @some_variable = 42 ensure puts "Ensure existent: #{(defined? @some_variable)}" puts "Ensure not_existent: #{(defined? @nonexistent_variable)}" end end 

它按预期工作:

 Before existent: Before not_existent: Ensure existent: Ensure not_existent: 

为什么?

首先要注意的是defined?关键字 ,而不是方法。 这意味着在构造语法树时(例如, ifreturnnext等)在编译期间由解析器专门处理 ,而不是在运行时动态查找。

这就是defined?原因defined? 可以处理通常会引发错误的表达式:已defined?(what is this even) #=> nil因为解析器可以从正常的评估过程中排除其参数。

真正令人困惑的是,即使它是一个关键字,它的行为仍然在运行时确定 。 它使用解析器魔术来确定其参数是实例变量,常量,方法等,然后调用普通的Ruby方法来确定是否在运行时定义了这些特定类型:

 // ... case DEFINED_GVAR: if (rb_gvar_defined(rb_global_entry(SYM2ID(obj)))) { expr_type = DEFINED_GVAR; } break; case DEFINED_CVAR: // ... if (rb_cvar_defined(klass, SYM2ID(obj))) { expr_type = DEFINED_CVAR; } break; case DEFINED_CONST: // ... if (vm_get_ev_const(th, klass, SYM2ID(obj), 1)) { expr_type = DEFINED_CONST; } break; // ... 

rb_cvar_defined函数与Module#class_variable_defined?调用的函数相同Module#class_variable_defined? , 例如。

如此defined? 太奇怪了。 真的很奇怪。 它的行为可能会有很大差异,具体取决于它的论点,我甚至不打赌它在不同的Ruby实现中是相同的。 基于此我建议不要使用它而是使用Ruby的*_defined? 方法尽可能。