Project

General

Profile

Actions

Bug #19795

closed

attr_accessor leading to nil values for re-assignment

Added by francktrouillez (Franck Trouillez) 9 months ago. Updated 9 months ago.

Status:
Closed
Assignee:
-
Target version:
-
ruby -v:
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-linux]
[ruby-core:114333]

Description

Steps to reproduce:

  • Create a class with an attr_accessor for an instance variable
  • Create an instance method that reassign this variable using the current value stored in the variable
  • Show that the variable is set to nil during the evaluation

Code snippet:

# attr_accessor_nil.rb

class A
  attr_accessor :a

  def initialize
    @a = 0
  end

  def my_method
    puts "a is '#{a.inspect}' of class '#{a.class}'"
    a += 1
  end
end

instance = A.new
instance.my_method

# output:
#
# a is '0' of class 'Integer'
# attr_accessor_nil.rb:12:in `my_method': undefined method `+' for nil:NilClass (NoMethodError)
#
#     a += 1
#       ^
# 	from attr_accessor_nil.rb:17:in `<main>'


Expected behavior

a += 1 should lead to a being equal to 1 at the end of the assignment, because a was storing 0 previously, as shown by the puts. Am I being wrong expecting this result?

Actual behavior

a += 1 raises an error about a being nil in the evaluation.

Further investigation

I checked if it was coming from the "instance variable", or about the "attr_accessor" by running the following snippet:

Code snippet:

# attr_accessor_nil.rb

class A
  attr_accessor :a

  def initialize
    @a = 0
  end

  def my_method
    puts "a is '#{a.inspect}' of class '#{a.class}'"
    @a += 1 # use the instance variable directly, instead of the accessor
  end
end

instance = A.new
instance.my_method

# output:
#
# a is '0' of class 'Integer'

This snippet runs just fine, and no error is raised.

System configuration

Ruby version : 3.2.2

Actions #1

Updated by francktrouillez (Franck Trouillez) 9 months ago

  • Description updated (diff)

Updated by nobu (Nobuyoshi Nakada) 9 months ago

  • Status changed from Open to Closed

This is an assignment to the local variable, not the accessor.

a += 1

You have to write:

self.a += 1

Updated by francktrouillez (Franck Trouillez) 9 months ago

I guess that it is a local variable, but is it the expected behavior?

As a developer, I expect that I can access a without the self.a.

Since the following code works as expected without self:

# attr_accessor_nil.rb

class A
  attr_accessor :a

  def initialize
    @a = 0
  end

  def my_method
    puts "a is '#{a.inspect}' of class '#{a.class}'"
    b = a + 1
    a = b
    puts "b is '#{b.inspect}' of class '#{b.class}'"
    puts "a is '#{a.inspect}' of class '#{a.class}'"
  end
end

instance = A.new
instance.my_method

# output:
#
# a is '0' of class 'Integer'
# b is '1' of class 'Integer'
# a is '1' of class 'Integer'

I expect that the following works as well:

# attr_accessor_nil.rb

class A
  attr_accessor :a

  def initialize
    @a = 0
  end

  def my_method
    puts "a is '#{a.inspect}' of class '#{a.class}'"
    a = a + 1
  end
end

instance = A.new
instance.my_method

# output:
#
# a is '0' of class 'Integer'
# Traceback (most recent call last):
# 	1: from attr_accessor_nil.rb:17:in `<main>'
# attr_accessor_nil.rb:12:in `my_method': undefined method `+' for nil:NilClass (NoMethodError)

Why can I access the instance variable (read and write) without the self, but as soon as I try to re-assign it using the current value, it switches to a local variable? This is something unexpected for me.

Is it the expected behavior for this? If so, is there a way of making this special case clearer? Or am I being wrong thinking the behavior is unexpected?

Updated by francktrouillez (Franck Trouillez) 9 months ago

I got it in the end thanks to a peer. I misunderstood wrongly how this was working:

Running the following snippet helped me understand:

# attr_accessor_nil.rb

class A
  attr_accessor :a

  def initialize
    @a = 0
  end

  def my_method
    puts "a is '#{a.inspect}' of class '#{a.class}'"
    b = a + 1
    a = b
    puts "b is '#{b.inspect}' of class '#{b.class}'"
    puts "a is '#{a.inspect}' of class '#{a.class}'"
  end

  def my_method_2
    puts "a is '#{a.inspect}' of class '#{a.class}'"
  end
end

instance = A.new
instance.my_method
instance.my_method_2

# output:
#
# a is '0' of class 'Integer'
# b is '1' of class 'Integer'
# a is '1' of class 'Integer'
# a is '0' of class 'Integer'

So, we cannot modify the instance variable through the accessor directly. If it was, the last puts should show 1

Thanks for your help!

Actions

Also available in: Atom PDF

Like0
Like0Like1Like0Like0