Dereferencing instance variables vs local variables/methods

Hello,

I think I don't agree/understand how instance variables work in ruby.

For example, when invoking a regular local variable and its name is not
defined, it raises appropriately a NameError:

a
=> NameError: undefined local variable or method `a' for main:Object

But when you do:

@a
# => nil

This is not a bug, but intended behavior, as we can see in rb_ivar_get
method:

VALUE
rb_ivar_get (obj, Id)
VALUE obj;
ID id;
{
VALUE val;

switches (TYPE (obj)) {
case T_OBJECT:
case T_CLASS:
case T_MODULE:
if (ROBJECT (obj) ->Iv_tbl &&
              St_lookup (ROBJECT (obj) ->Iv_tbl, Id, &val) )
return val;
break;
default:
if (FL_TEST (obj, FL_EXIVAR) || rb_special_const_p (obj))
return generic_ivar_get (obj, Id) ;
break;
}
*rb_warning ("instance variable %s not initialized",
             Rb_id2name (id));

return Qnil;*
}
(variable.c)

Why should Ruby issue a warning and return nil instead of just raising an error? There's probably a good reason for that but I can't see it.

Consider something like this

class Foo
  def set_bar(value)
    @bar = value
  end

  def some_method
    @bar ? "@bar is not nil or false" : "@bar is not set"
  end
end

If ruby raises an error when you call some_method before set_bar, you are
forced to write a initialize method with @bar = nil in it.

···

2011/3/31 Josep M. Bach <josep.m.bach@gmail.com>

Hello,

I think I don't agree/understand how instance variables work in ruby.

For example, when invoking a regular local variable and its name is not
defined, it raises appropriately a NameError:

>> a
>> => NameError: undefined local variable or method `a' for main:Object

But when you do:

>> @a
>> # => nil

This is not a bug, but intended behavior, as we can see in rb_ivar_get
method:

VALUE
rb_ivar_get (obj, Id)
VALUE obj;
ID id;
{
VALUE val;

switches (TYPE (obj)) {
case T_OBJECT:
case T_CLASS:
case T_MODULE:
if (ROBJECT (obj) ->Iv_tbl &&
              St_lookup (ROBJECT (obj) ->Iv_tbl, Id, &val) )
return val;
break;
default:
if (FL_TEST (obj, FL_EXIVAR) || rb_special_const_p (obj))
return generic_ivar_get (obj, Id) ;
break;
}
*rb_warning ("instance variable %s not initialized",
             Rb_id2name (id));

return Qnil;*
}
(variable.c)

Why should Ruby issue a warning and return nil instead of just raising an error? There's probably a good reason for that but I can't see it.

Josep M. Bach wrote in post #990178:

Hello,

I think I don't agree/understand how instance variables work in ruby.

There is also this trickery as well:

if 1 > 5
  x = 10
end

puts x #nil

···

--
Posted via http://www.ruby-forum.com/\.

Regarding this issue I have come to the same conclusion as Gunther.

On one side of the balance we get some flexibility and we are allowed to not have to initialize all class variables in the initialize method on the other we are exposing ourselves that if at any time this changes we might have a lot of non working software on our hands. ( which is highly unlikely).

Still I prefer it like it is, it is a sort of convention over configuration. ( You wan't nil ? Don't do anything its already done for you)

···

On Mar 31, 2011, at 1:44 PM, Gunther Diemant wrote:

Consider something like this

class Foo
def set_bar(value)
   @bar = value
end

def some_method
   @bar ? "@bar is not nil or false" : "@bar is not set"
end
end

If ruby raises an error when you call some_method before set_bar, you are
forced to write a initialize method with @bar = nil in it.

2011/3/31 Josep M. Bach <josep.m.bach@gmail.com>

Hello,

I think I don't agree/understand how instance variables work in ruby.

For example, when invoking a regular local variable and its name is not
defined, it raises appropriately a NameError:

a
=> NameError: undefined local variable or method `a' for main:Object

But when you do:

@a
# => nil

This is not a bug, but intended behavior, as we can see in rb_ivar_get
method:

VALUE
rb_ivar_get (obj, Id)
VALUE obj;
ID id;
{
VALUE val;

switches (TYPE (obj)) {
case T_OBJECT:
case T_CLASS:
case T_MODULE:
if (ROBJECT (obj) ->Iv_tbl &&
             St_lookup (ROBJECT (obj) ->Iv_tbl, Id, &val) )
return val;
break;
default:
if (FL_TEST (obj, FL_EXIVAR) || rb_special_const_p (obj))
return generic_ivar_get (obj, Id) ;
break;
}
*rb_warning ("instance variable %s not initialized",
            Rb_id2name (id));

return Qnil;*
}
(variable.c)

Why should Ruby issue a warning and return nil instead of just raising an error? There's probably a good reason for that but I can't see it.

It's good advice to always initialize all of your instance variables
before attempting to use them. You don't have to initialize them in
your constructor, but failing to initialize them at all will generate
warnings upon reference when ruby is run with the -w option:

$ export RUBYOPT=-w

$ irb
irb(main):001:0> class Foo
  end
end
irb(main):002:1> def set_bar(value)
irb(main):003:2> @bar = value
irb(main):004:2> end
irb(main):005:1>
irb(main):006:1* def some_method
irb(main):007:2> @bar ? "@bar is not nil or false" : "@bar is not set"
irb(main):008:2> end
irb(main):009:1> end
=> nil
irb(main):010:0> Foo.new.some_method
(irb):7: warning: instance variable @bar not initialized
=> "@bar is not set"

Modules make things a bit tricky if you want them to use their own
instance variables. In these cases, you should probably use the
defined? operator to ensure that if the instance variable is undefined
that you initialize it then:

module Foo
  def set_bar(value)
    @bar = value
  end

  def some_method
    "@bar = '#{__bar}'"
  end

  private

  # Always use the to reference @bar's value so that it is sure
  # to be initialized.
  def __bar
    @bar = "some value" unless defined?(@bar)
    @bar
  end
end

class A
  include Foo
end

a = A.new
puts a.some_method

a = A.new
a.set_bar("foobar")
puts a.some_method

Running the above with ruby -w should not print any warnings.

-Jeremy

···

On 3/31/2011 06:44, Gunther Diemant wrote:

Consider something like this

class Foo
  def set_bar(value)
    @bar = value
  end

  def some_method
    @bar ? "@bar is not nil or false" : "@bar is not set"
  end
end

If ruby raises an error when you call some_method before set_bar, you are
forced to write a initialize method with @bar = nil in it.

7stud -- wrote in post #990282:

Josep M. Bach wrote in post #990178:

Hello,

I think I don't agree/understand how instance variables work in ruby.

By rule, instance and global variables are initialized to nil by
default, and local variables aren't

Local variables *are* initialized to nil.

But because Ruby doesn't require you to put parentheses after a method
call - e.g. you can write 'puts' instead of 'puts()' - it needs a way to
decide whether the bareword 'puts' is actually a local variable or a
method call.

This is decided statically, when the code is being read in and parsed
(but before it is run).

Within a particular scope (remember that 'class' and 'def' start a new
scope), if an assignment to that word has been seen earlier, then that
word is treated as a local variable.

Hence:

def foo
  puts = 123
  puts
end

returns 123 (and doesn't print anything), whereas

def foo
  puts
end

prints a blank line (and returns nil, which is the return value of the
'puts' method)

--but there is some local variable
trickery here:

if 1 > 5
  x = 10
end

puts x #nil

It doesn't matter whether the assignment is executed or not. The
statement "x = 10" has been seen, and so from that point onwards a
bareword "x" is treated as a local variable rather than a method.

If there is no assignment, then a method call is attempted:

puts y # like puts(y()), tries to call method y

But if that fails, it could mean either that you forgot to define method
'y', or that you forgot to assign to local variable 'y'. Hence the error
message says:

NameError: undefined local variable or method `y' for main:Object
                     ^^^^^^^^^^^^^^^^^^^^^^^^

Incidentaly, you can always force a method call using parentheses or
send or dot notation:

  puts = 123
  puts # returns 123
  puts() # calls method
  send :puts # calls method
  self.puts # calls method (only if not private)
  puts(puts) # prints 123 :slight_smile:

Regards,

Brian.

···

--
Posted via http://www.ruby-forum.com/\.

I can see the point. It's all about tradeoffs I guess (with this behavior
you have accessors automatically initialized to nil).
Very interesting thoughts on this, thanks guys :slight_smile: