Argument Passing in Ruby
- Evaluation Strategies and Method Arguments
- Argument Passing: Call-by-Value vs. Call-by-Reference
- Language vs. Implementation
- Call-by-Sharing
- References
A recurring discussion among Ruby programmers of all skill levels is the way that Ruby passes objects to methods. After a bit of time with Ruby, it becomes clear—sometimes painfully so—that when a method mutates its arguments, those changes are visible outside of the method. The following example (shown also in Figure 1) should trigger an unpleasant flashback to the point when you first realized this fact:
def log_message (message) message << “at ” + Time.now.to_s $stdout.puts(message) end home = File.expand_path(“~”) log_message(home) # home is mutated. # This won't work as expected. Dir.entries(home) do |entry| # ... end
The problem with this example code is obvious. The log_message method appends a string to its argument, which in turn affects the home variable as well. But this article isn't about the surprises encountered when objects are mutated; that's a topic I cover plenty of times in Effective Ruby: 48 Specific Ways to Write Better Ruby. [1] For the sake of our objective, let's figure out why it's even possible to modify a method's arguments in ways that are externally visible.
- For convenience in following along with this discussion, download a zip file containing the complete source files for this article.
Evaluation Strategies and Method Arguments
Argument passing falls under the subject of expression evaluation. Most mainstream programming languages, but certainly not all, use eager evaluation. In the context of calling a method, this means that arguments are evaluated before a method's body begins executing. In Ruby it's fairly common to call methods and supply compound expressions instead of variable names. As an example, consider this call to the log_message method (Figure 2):
log_message(“checking directory ” + dir)
Prior to executing the log_message method, the Ruby interpreter will evaluate the expression given in the argument list. In this case, the expression creates a new string object that isn't bound to any variable. Then, just before the method executes, Ruby binds it to a local variable that is available inside the method body. This is the object of interest. Whether it's a complex expression or simply a variable name, we want to know how these values pass through the boundary between the caller's scope and the method's scope. To accomplish this task, we should nail down some terminology that will help us to distinguish between these two scopes.
According to the Concise Encyclopedia of Computer Science, [2] when we define a method (using the def keyword) and name a method's arguments, those names are referred to as the method's formal arguments. In Ruby, formal arguments become local variables inside the body of a method.
On the flip side, when we call a method, the values passed as arguments are known as a method's actual arguments. It's important to remember that in Ruby, values are always objects. Therefore, a method's actual arguments and its formal arguments are always objects. The confusing part is how those two are connected. “How do actual arguments get mapped into formal arguments?“ I'm glad you asked. Let's try to get to the bottom of this puzzle.