多重代入と連鎖した代入との違い

多重代入について(8409)|teratail
で回答したものを補足して引用。



質問:
a, b = 1, 1a = b = 1 のときに、どちらでも a += 1; b += 2 とすると a2b3になるので、両者は同じ意味か?

回答:
違います

a, b = 1, 1

は多重代入で、ab それぞれが別のオブジェクトですが、

a = b = 1

a = (b = 1)

です。つまり b = 1 である ba に代入しています(代入 = は右結合性です)。このため ab は等値になります。
Object#object_id を使って

a = b = "a"
#=> "a"
a.object_id == b.object_id
#=> true
a, b = "a", "a"
#=> ["a", "a"]
a.object_id == b.object_id
#=> false

と確かめることも出来ます。
同様に、二つのオブジェクトが同一のものかどうか調べる時に使用する Object#equal?

a = b = "a"
#=> "a"
a.equal?(b)
#=> true
a, b = "a", "a"
#=> ["a", "a"]
a.equal?(b)
#=> false

でも確認できます。

さて、

a += 1

a = a + 1

なのはご存じだと思いますが、ここで使われている + 演算子(これもメソッドです)は 新しい オブジェクト(この場合は 2 )を作ります。それを a に代入しているので、もとの a と、代入されたあとの a とは異なります。

例えば

a = b = "a"

のときに、文字オブジェクトを 破壊的に 書き替えるメソッドとして String#upcase! を使うと次のようになります。

a = b = "a"
#=> "a"
a.upcase!
#=> "A"
a
#=> "A"
b
#=> "A" # こちらも書き換わっている

一方、多重代入の時には ab との間に関係が生じません。

a, b = "a", "a"
#=> ["a", "a"]
a.upcase!
#=> "A"
a
#=> "A"
b
#=> "a" # こちらは書き換わっていない

なお、数やシンボルには破壊的なメソッドが存在しない(というか破壊できない)ので a, b = 1, 1a = b = 1 も同じように振る舞います。
整数のオブジェクト1はどれも全く同じオブジェクトですので、

a = b = 1
#=> 1
a.equal?(b)
#=> true
a, b = 1, 1
#=> [1, 1]
a.equal?(b)
#=> true

となります。
ですが、数やシンボル以外のオブジェクトを扱うときには注意しないといけません。



実を言えば、この質問を見たときにこの回答になると分かっていませんでした。
自分の中でも、この「連鎖した代入」のときに問題が生じない場合(数やシンボルなど変更不可な (Immutable) オブジェクト)と問題が生じる場合との区別が付いていませんでした。
このように書き出したことで、自分でも納得できるように説明が出来ました。これで間違えることは今後無いと思います。