Рубин определен? оператор работает неправильно?

Итак, у нас есть код:

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

А теперь вопрос - почему? Мы вызвали исключение до определения some_variable. Почему это работает именно так? Почему 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:

Почему?


person mr.The    schedule 31.03.2015    source источник
comment
Ссылки на неопределенные переменные экземпляра (и глобальные) обрабатываются иначе, чем ссылки на неопределенные локальные (и классовые) переменные. Например, puts @a #=> nil, тогда как puts a #NameError: undefined local variable or method a для main:Object`.   -  person Cary Swoveland    schedule 31.03.2015


Ответы (1)


Первое, на что следует обратить внимание, это то, что defined? — это ключевое слово, а не метод. Это означает, что он специально обрабатывается синтаксическим анализатором во время компиляции, когда синтаксическое дерево строится (так же, как if, return, next и т. д.), а не динамически просматривается во время выполнения.

Вот почему 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?< /а>, например.

Итак, defined? странный. Очень странно. Его поведение может сильно различаться в зависимости от его аргумента, и я бы даже не стал ставить на то, что оно будет одинаковым в разных реализациях Ruby. Исходя из этого, я бы рекомендовал не использовать его и вместо этого использовать методы *_defined? Ruby везде, где это возможно.

person Max    schedule 31.03.2015
comment
Вот это да. Это странно. У вас есть какой-нибудь документ или подробности об этом? - person mr.The; 31.03.2015
comment
Очень интересно! Спасибо. - person Cary Swoveland; 31.03.2015
comment
Спасибо за подробный ответ! - person mr.The; 31.03.2015