8

What's the point of instance_variable_set? Aren't these two lines the same?

instance_variable_set(@name, value)
@name = value"
sawa
  • 165,429
  • 45
  • 277
  • 381
Fei Qu
  • 121
  • 1
  • 9
  • Possible duplicate of [How to properly set an instance variable with instance\_variable\_set?](https://stackoverflow.com/questions/12505289/how-to-properly-set-an-instance-variable-with-instance-variable-set) – matt Oct 28 '18 at 02:26
  • 1
    @matt, close, but the linked question is only asking why the colon is necessary. – Cary Swoveland Oct 28 '18 at 04:29
  • 2
    if you used `instance_variable_set("@name", value)` they would be the same. (note the quotation marks around `@name`) – max pleaner Oct 28 '18 at 06:44

5 Answers5

12

In the case of a "simple" variable assignment for an instance variable like:

@foo = "foo"

You couldn't do

"@#{foo}" = "bar" # syntax error, unexpected '=', expecting end-of-input

But you could do something similar with instance_variable_set:

instance_variable_set("@#{foo}", "bar")
p @foo # "bar"

As per your question Aren't these two lines the same?, for that example they're similar, but isn't the use people tend to give to instance_variable_set.

Sebastián Palma
  • 32,692
  • 6
  • 40
  • 59
  • 1
    As stated here, as well as the obvious benefit/consequence of ignoring whether there is a public `attr_writer` or `attr_accessor`. A publicly unexposed instance variable can be set using this. – ForeverZer0 Oct 28 '18 at 03:56
6

I wonder why there is no mention of another obvious difference: they have different scopes.

While @name = value is accessible only from within the scope where the instance variable is defined (read: from inside the instance,) instance_variable_set is available from everywhere to set instance variables from outside:

class C
  attr_reader :name
  def initialize(name)
    @name = name
  end
end

C.new("foo").tap do |c|
  c.instance_variable_set(:@name, 42)
  c.name
end
#⇒ 42
Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • I interpreted the OP as asking why one would not just rely on setters for changing the values of instance variables outside the class, but I now see the code in the question may suggest otherwise. – Cary Swoveland Oct 28 '18 at 09:24
3

I'm pretty new to Ruby and have this question when I'm reading some tutorial. I'm curious what's the point of instance_variable_set?

The point of Object#instance_variable_set is to dynamically reflectively set an instance variable whose name may not be known at design time, only at run time.

Aren't these two lines the same?

instance_variable_set(@name, value)
@name = value

No, these lines are completely different, and they perfectly illustrate what I wrote above:

  • The first line sets the instance variable whose name is stored inside @name to value.
  • The second line sets the instance variable @name to value.
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
1

From the fine manual:

instance_variable_set(symbol, obj) → obj
instance_variable_set(string, obj) → obj

Sets the instance variable named by symbol to the given object, thereby frustrating the efforts of the class's author to attempt to provide proper encapsulation. The variable does not have to exist prior to this call. If the instance variable name is passed as a string, that string is converted to a symbol.

So the first argument isn't @name, it is :@name (i.e. a Symbol) or '@name' (a String).

The result is that instance_variable_set, as noted in the documentation, can be used to set an instance variable when you know its name even if you don't know the name until your code is running.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • μ, thanks for bringing that doc to my attention. It prompted me to post a suggestion at [Ruby trunk](https://bugs.ruby-lang.org/issues/15265?next_issue_id=15264). :-) – Cary Swoveland Oct 28 '18 at 04:23
1

Here is an example of how the methods Object#instance_variable_set and Object#instance_variable_get could be used to increment the values of all instance variables by one.

class Klass
  attr_accessor :a, :b, :cat
  def initialize
    @a, @b, @c, @d = 1, 2, 3, 4
  end
end

k = Klass.new
  #=> #<Klass:0x0000000001d70978 @a=1, @b=2, @c=3, @cat=4>
k.instance_variables.each { |v| k.instance_variable_set(v, k.instance_variable_get(v)+1) }
  #=> [:@a, :@b, :@c, :@cat]
k #=> #<Klass:0x0000000001d70978 @a=2, @b=3, @c=4, @cat=5>

See also Object#instance_variables.

Compared to having four separate assignment statements, fewer lines of code are needed, but there are two other, more important advantages:

  • there is less chance of introducing a bug (k.cut += 1); and
  • adding, removing or renaming instance variables does not require the value-incrementing code to be changed.

A variant of this is to substitute a dynamically-constructed array of instance variable names (e.g., [:@a, :@b]) for instance_variables above.

These may seem like unusual examples, but they are representative of a large class of operations involving instance variables in which this kind of batch processing can be used to advantage.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100