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 [email protected]

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)

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–but there is some local variable
trickery here:

if 1 > 5
x = 10
end

puts x #nil

On 3/31/2011 06:44, Gunther D. wrote:

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.

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

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.

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: