Ruby “defined?” operator works wrong?

佐手、 提交于 2019-12-23 08:35:51

问题


So, we have the code:

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

And call it from irb:

> Foo.new.bar

And, that is will return:

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

And now is question - why? We raised exception before than some_variable be defined. Why it works this way? Why some_variable is defined in ensure block? (btw, it defined as nil)

UPDATE: Thanks @Max for answer, but if we change code to use instance variable:

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

It works as expected:

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

Why?


回答1:


The first thing to notice is that defined? is a keyword, not a method. That means that is specially handled by the parser during compilation when the syntax tree is constructed (just like if, return, next, etc.) rather than dynamically looked up at runtime.

This is why defined? can handle expressions that would normally raise an error: defined?(what is this even) #=> nil because the parser can exclude its argument from the normal evaluating process.

The really confusing bit is that even though it is a keyword, its behavior is still determined at runtime. It uses parser magic to determine whether its argument is an instance variable, constant, method, etc. but then calls normal Ruby methods to determine whether these specific types have been defined at runtime:

// ...
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;
// ...

That rb_cvar_defined function is the same one called by Module#class_variable_defined?, for example.

So defined? is weird. Really weird. Its behavior could vary a lot depending on its argument, and I wouldn't even bet on it being the same across different Ruby implementations. Based on this I would recommend not using it and instead use Ruby's *_defined? methods wherever possible.



来源:https://stackoverflow.com/questions/29371966/ruby-defined-operator-works-wrong

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!