Seth Modu
Seth Modu

Reputation: 1

Referencing array index in Ruby

I was under impression that references in Ruby are equivalent to pointers in C. But I'm confused by this:

ar = [1, 2, 3, 4]
first = ar[0] # => 1
ar.shift # => 1
ar # => [2, 3, 4]
first # => 1

Shouldn't first point to the memory address of ar[0], which holds the value 2? Why does it still hold to the expired value?

What is the Ruby way to let first equal to the current value of ar[0]?

Upvotes: 0

Views: 780

Answers (2)

engineersmnky
engineersmnky

Reputation: 29598

What is the Ruby way to let first equal to the current value of ar[0]?

If you really wanted to you could make first a method rather than a local variable such as

ar = [1, 2, 3, 4]
define_method(:first, &ar.method(:first))
first #=> 1
ar.shift # => 1
ar # => [2, 3, 4]
first # => 2

Note: While this answers your question I am not endorsing this as a viable concept since a subsequent definition of first e.g. (first = 12) will result in first #=> 12 (local variable) and first() #=> 2 (method call)

Upvotes: 0

Nate
Nate

Reputation: 2404

You assigned the variable first to whatever the first element was at the time. Since ar[0] was 1 at the time, it was exactly the same as just saying first = 1.

It’s not the same as calling ar.first, which always points to the first element (synonymous with ar[0]).

Ruby uses what it calls an object_id to keep track of each object instance. When you say first = ar[0], Ruby assigns first to the same object that's in ar[0]. These two objects will remain in sync, and any mutations that you perform to them will stay in sync, since it's exactly the same object in memory. If you re-assign either variable (ar[0] or first) to a different object, you'll lose your sync.

irb(main):001:0> ar = ['a','b','c']
=> ["a", "b", "c"]
irb(main):002:0> first = ar[0]
=> "a"
irb(main):003:0> ar[0] << ' testing!'
=> "a testing!"
irb(main):004:0> first
=> "a testing!"
irb(main):005:0> ar[0].replace('this is a test')
=> "this is a test"
irb(main):006:0> ar
=> ["this is a test", "b", "c"]
irb(main):007:0> first
=> "this is a test"
irb(main):008:0> ar[0].object_id
=> 70159033810680
irb(main):009:0> first.object_id
=> 70159033810680
irb(main):010:0> ar[0] = 'different string instance'
=> "different string instance"
irb(main):011:0> ar
=> ["different string instance", "b", "c"]
irb(main):012:0> first
=> "this is a test"
irb(main):013:0> ar[0].object_id
=> 70159033712320

The << method and replace methods mutate the string object. If you're using Rails and familiar with ActiveRecord, it'd be like doing:

user1 = User.new(first_name: 'Bob')
user2 = User.new(first_name: 'Joe')
user3 = User.new(first_name: 'Dave')

ar = [user1, user2, user3]
first = ar[0]

In the end, all you did was first = user1

If you do user1.first_name = 'Jim', you'll now see that ar[0], user1, and first all had the first_name changed, because you mutated the ActiveRecord object.

Numbers are immutable. You cannot change the object that's in memory. You cannot change a 1 to be a 5. All you can do is update your variable to use a different object instance.

irb(main):014:0> testing = 1 + 5
=> 6
irb(main):015:0> testing.object_id
=> 13
irb(main):016:0> 6.object_id
=> 13

6 is basically a constant. You can't change 1, you can't change 5, but you can add them together to get 6.

Quick side note about object_id. Some of the Ruby core object ids are pretty constant, but just pretend they aren't predictable. They're meant for you to be able to compare two objects easily. Some functions in Ruby user the object_id to determine equality, because if the object_id of two different variables are the same, then they are definitely equal, since they point to the same memory location.

Any time you create write string literal, you create a new object in memory.

irb(main):017:0> a = 'testing'
=> "testing"
irb(main):018:0> b = 'testing'
=> "testing"
irb(main):019:0> a.object_id
=> 70159025508120
irb(main):020:0> b.object_id
=> 70159029161840

If you mutate a, then b does not change because they are different objects. If you're passing a string variable around, it's important to know whether the action you're calling is mutative or not, and whether or not you actually want mutation.

For example, you may at some point in life create an array.

ar = ['a','b','c']
=> ["a", "b", "c"]

And then decide you want to use pass that first element of the array into a function argument, or store it in a variable.

first = ar[0]
=> "a"

This is fine as long as you don't mutate. Well, maybe you're wanting to change your first variable for some reason. Perhaps you're building a list of HTML classes.

first << ' some cool text'
=> "a some cool text"

first
=> "a some cool text"

All seems fine, right?

But, look what that did to your array.

ar
=> ["a some cool text", "b", "c"]

If you see a time when you want to mutate a variable that was passed in, one thing you can do is assign your variable to a copy of it.

first = ar[0].dup

Now, first is the same string value as ar[0], but a new string instance. You can safely mutate first or ar[0] without changing the other one.

Mutation isn't always bad. It can be very helpful. But, unexpected mutations can be really confusing if you don't know they're happening. Especially if you're mutating an argument to a function, because the original value was defined elsewhere and now it's changed.

Ruby tends to end method names with ! if there's a mutable version of it. Like sub vs sub! in a string. sub returns a new string with your substitutions applied, leaving the original string alone. sub! modifies (aka mutates) the string you call it on.

Upvotes: 1

Related Questions