How to pass by reference in Ruby?

2019-06-14 19:30发布

问题:

Currently I'm developing a Watcher class in Ruby, which, among other things, is able to find the period duration of a toggling signal. This is in general rather simple, but a problem I'm facing is that apparently Ruby passes all parameters by value. While researching online I found many different discussions about what "pass by value" and "pass by reference" actually is, but no actual "how to". Coming from a C/C++ background, for me this is an essential part of a programming/scripting language.

The relevant parts of the class that I wrote are shown below. The method Watcher::watchToggling() is the one I'm having trouble with. As a parameter, aVariable should be the reference to the value I'm measuring the period duration for.

class Watcher
  @done
  @timePeriod

  def reset()
    @done = false;
    @timePeriod = nil;
  end

  def watchToggling(aVariable, aTimeout=nil)
    oldState = aVariable;
    oldTime = 0;
    newTime = 0;

    timeout(aTimeout)do
      while(!@done)
        newState = aVariable;
        if(newState != oldState)
          if(newState == 1)
            # rising edge
            if(oldTime != 0)
              # there was already a rising edge before,
              # so we can calculate the period
              newTime = Time.now();
              @timePeriod = newTime - oldTime;
              @done = true;
            else
              # if there was no previous rising edge,
              # set the time of this one
              oldTime = Time.now();
            end
          end
          oldState = newState;
        end
      end
    end

    puts("Watcher: done.")
  end

  def isDone()
    return @done;
  end

  def getTimePeriod()
    return @timePeriod;
  end
end

Is there any way at all in ruby to pass by reference? And if not, are there alternatives that would solve this problem?

回答1:

Ruby is strictly pass-by-value, which means references in the caller's scope are immutable. Obviously they are mutable within the scope, since you can assign to them after all, but they don't mutate the caller's scope.

a = 'foo'

def bar(b)
  b = 'bar'
end

bar(a)

a
# => 'foo'

Note, however, that the object can be mutated even if the reference cannot:

a = 'foo'

def bar(b)
  b << 'bar' # the only change: mutate the object instead of the reference
  b = 'baz'
end

bar(a)

a
# => 'foobar'

If the object you get passed is immutable, too, then there is nothing you can do. The only possibilities for mutation in Ruby are mutating the reference (by assignment) or asking an object to mutate itself (by calling methods on it).

You can return an updated value from your method and have the caller assign that to the reference:

a = :foo

def bar(b)
  :"#{a}bar"
end

c = bar(a)

c
# => :foobar

Or you can wrap the immutable object in a mutable one and mutate that mutable wrapper:

a = [:foo]

def bar(b)
  b[0] = :bar
end

bar(a)

a
# => [:bar]

[This is really the same thing as the second example above.]

But if you can't change anything, then you are stuck.