Section 11.4. Conclusion


11.3. Working with Dynamic Features

Skynet became self-aware at at 2:14am EDT August 29, 1997.

Terminator 2: Judgment Day

Many of you will come from the background of a static language such as C. To those readers, we will address this rhetorical question: Can you imagine writing a C function that will take a string, treat it as a variable name, and return the value of the variable?

No? Then can you imagine removing or replacing the definition of a function? Can you imagine trapping calls to nonexistent functions? Or determining the name of the calling function? Or automatically obtaining a list of user-defined program elements (such as a list of all your functions)?

Ruby makes this sort of thing possible. This runtime flexibility, the capability to examine and change program elements at runtime, makes many problems easier. A runtime tracing utility, a debugger, and a profiling utility are all easy to write for Ruby and in Ruby. The well-known programs irb and xmp both use dynamic features of Ruby to perform their magic.

These capabilities take getting used to, and they are easy to abuse. But the concepts have been around for many years (they are at least as old as LISP) and are regarded as "tried and true" in the Scheme and Smalltalk communities as well. Even Java, which owes so much to C and C++, has some dynamic features, so we expect this way of thinking to increase in popularity as time goes by.

11.3.1. Evaluating Code Dynamically

The global function eval compiles and executes a string that contains a fragment of Ruby code. This is a powerful (if slightly dangerous) mechanism because it allows you to build up code to be executed at runtime. For example, the following code reads in lines of the form name = expression; it then evaluates each expression and stores the result in a hash indexed by the corresponding variable name.

parameters = {} ARGF.each do |line|   name, expr = line.split(/\s*=\s*/, 2)   parameters[name] = eval expr end


Suppose the input contained these lines:

a = 1 b = 2 + 3 c = `date`


Then the hash parameters would end up with the value {"a"=>1, "b"=>5, "c"=>"Mon Apr 30 21:17:47 CDT 2001\n"}. This example also illustrates the danger of evaling strings when you don't control their contents; a malicious user could put d=`rm *` in the input and ruin your day.

Ruby has three other methods that evaluate code "on the fly": class_eval, module_eval, and instance_eval. The first two are synonyms, and all three do effectively the same thing; they evaluate a string or a block, but while doing so they change the value of self to their own receiver. Perhaps the most common use of class_eval allows you to add methods to a class when all you have is a reference to that class. We use this in the hook_method code in the TRace example in section 11.3.13, "Tracking Changes to a Class or Object Definition." You'll find other examples in the more dynamic library modules, such as delegate.rb.

The eval method also makes it possible to evaluate local variables in a context outside their scope. We don't advise doing this lightly, but it's nice to have the capability.

Ruby associates local variables with blocks, with high-level definition constructs (class, module, and method definitions), and with the top-level of your program (the code outside any definition constructs). Associated with each of these scopes is the binding of variables, along with other housekeeping details. Probably the ultimate user of bindings is irb, the interactive Ruby shell, which uses bindings to keep the variables in the program that you type in separate from its own.

You can encapsulate the current binding in an object using the method Kernel#binding. Having done that, you can pass the binding as the second parameter to eval, setting the execution context for the code being evaluated.

def some_ethod   a = "local variable"   return binding end the_binding = some_method eval "a", the_binding   # "local variable"


Interestingly, the presence of a block associated with a method is stored as part of the binding, enabling tricks such as this:

def some_method   return binding end the_binding = some_method { puts "hello" } eval "yield", the_binding                # hello


11.3.2. Using const_get

The const_get method retrieves the value of a constant (by name) from the module or class to which it belongs.

str = "PI" Math.const_get(str)     # Evaluates to Math::PI


This is a way of avoiding the use of eval, which is sometimes considered inelegant. This type of solution is better code, it's computationally cheaper, and it's safer. Other similar methods are instance_variable_set, instance_variable_get, and define_method.

It's true that const_get is faster than eval. In informal tests, const_get was roughly 350% as fast; your results will vary. But is this really significant? The fact is, the test code ran a loop 10 million times to get good results and still finished under 30 seconds.

The usefulness of const_get is that it is easier to read, more specific, and more self-documenting. This is the real reason to use it. In fact, even if it did not exist, to make it a synonym for eval would still be a large step forward.

See also the next section (section 11.3.3, "Dynamically Instantiating a Class by Name") for another trick.

11.3.3. Dynamically Instantiating a Class by Name

We have seen this question more than once. Given a string containing the name of a class, how can we create an instance of that class?

The proper way is with const_get, which we just saw. All classes in Ruby are normally named as constants in the "global" namespacethat is, members of Object.

classname = "Array" klass = Object.const_get(classname) x = klass.new(4, 1)        # [1, 1, 1, 1]


What if the names are nested? It turns out this doesn't work:

class Alpha   class Beta     class Gamma       FOOBAR = 237     end   end end str = "Alpha::Beta::Gamma::FOOBAR" val = Object.const_get(str)          # error!


This is because const_get just isn't "smart" enough to recognize nested names of this sort. The following example is an idiom that will work well, however:

# Same class structure str = "Alpha::Beta::Gamma::FOOBAR" val = str.split("::").inject(Object) {|x,y| x.const_get(y) }  # 237


This is a commonly needed piece of code (and a neat use of inject).

11.3.4. Getting and Setting Instance Variables

In accordance with our desire to use eval only when necessary, Ruby now has methods that can retrieve or assign instance variable values, given the variable name as a string.

class MyClass   attr_reader :alpha, :beta   def initialize(a,b,g)     @alpha, @beta, @gamma = a, b, g   end end x = MyClass.new(10,11,12) x.instance_variable_set("@alpha",234) p x.alpha                                   # 234 x.instance_variable_set("@gamma",345)       # 345 v = x.instance_variable_get("@gamma")       # 345


Note first of all that we do have to use the at-sign on the variable name; not to do so is an error. If this is unintuitive, remember that methods such as attr_accessor actually take a symbol used to name the methods, which is why they omit the at-sign.

You may wonder whether the existence of these methods is a violation of encapsulation. The answer is no.

It's true these methods are powerful and potentially dangerous. They should be used cautiously, not casually. But it's impossible to say whether encapsulation is violated without looking at how these tools are used. If they are used intentionally as part of a good design, then all is well. If they are used to violate the design, or to circumvent a bad design, then all is not well. Ruby intentionally grants access to the interiors of objects for people who really need it; the mark of the responsible programmer is not to abuse that freedom.

11.3.5. Using define_method

Other than def, define_method is the only normal way to add a method to a class or object; the latter, however, enables you to do it at runtime.

Of course, essentially everything in Ruby happens at runtime. If you surround a method definition with puts calls, as in the following example, you can see that.

class MyClass   puts "before"   def meth     #...   end   puts "after" end


But within a method body or similar place, we can't just reopen a class (unless it's a singleton class). In such a case, we had to use eval in older versions of Ruby; now we have define_method. It takes a symbol (for the name of the method) and a block (for the body of the method).

A first attempt to use it might look like this (which has an error):

# This doesn't work because define_method is private if today =~ /Saturday|Sunday/   define_method(:activity)  { puts "Playing!" } else   define_method(:activity)  { puts "Working!" } end activity


Because define_method is private, we have to do this:

# Works fine (Object is context at top level) if today =~ /Saturday|Sunday/   Object.class_eval { define_method(:activity)  { puts "Playing!" } } else   Object.class_eval { define_method(:activity)  { puts "Working!" } } end activity


We could also do this inside a class definition (with Object or any other class). It's rare that this will be justifiable, but if you can do it inside the class definition, privacy is not an issue.

class MyClass   define_method(:mymeth) { puts "This is my method." } end


There is also a trick where you give a class a method that indirectly exposes define_method's functionality.

class MyClass   def self.new_method(name, &block)     define_method(name, &block)   end end MyClass.new_method(:mymeth) { puts "This is my method." } x = MyClass.new x.mymeth           # Prints "This is my method."


The same could be done at the instance level rather than the class level:

class MyClass   def new_method(name, &block)     self.class.send(:define_method,name, &block)   end end x = MyClass.new x.new_method(:mymeth) { puts "This is my method." } x.mymeth           # Prints "This is my method."


Here we're still defining an instance method dynamically. Only the means of invoking new_method has changed. Note the send trick that we use to circumvent the privacy of define_method. This works because send in current Ruby versions always allows you to call private methods. (This is another "loophole," as some would call it, that has to be used responsibly.)

It's important to realize another fact about define_method: It takes a block, and a block in Ruby is a closure. That means that, unlike an ordinary method definition, we are capturing context when we define the method. The following example is useless, but it illustrates the point:

class MyClass   def self.new_method(name, &block)     define_method(name, &block)   end end a,b = 3,79 MyClass.new_method(:compute) { a*b } x = MyClass.new puts x.compute           # 237 a,b = 23,24 puts x.compute           # 552


The point is that the new method can access variables in the original scope of the block, even if that scope "goes away" and is otherwise inaccessible. This will be useful at times, perhaps in a metaprogramming situation or with a GUI toolkit that allows defining methods for event callbacks.

Note that the closure is only a closure when the variable name is the same. On rare occasions, this can be tricky. Here we use define to expose a class variable (not really the way we should do it, but it illustrates the point):

class SomeClass   @@var = 999   define_method(:peek) { @@var } end x = SomeClass.new p x.peek             # 999


Now let's try this with a class instance variable:

class SomeClass   @var = 999   define_method(:peek) { @var } end x = SomeClass.new p x.peek             # prints nil


We expect 999 but get nil instead. Why is that? I'll defer the explanation for a moment.

Observe, on the other hand, this works fine:

class SomeClass   @var = 999   x = @var   define_method(:peek) { x } end x = SomeClass.new p x.peek      # 999


So what is happening? Well, it is true that a closure captures the variables from its context. But even though a closure knows the variables from its scope, the intended context of the new method is the context of the instance of the object, not the class itself.

Since @var in that context would refer to an instance variable of the object, not the class, the class instance variable is overridden (hidden) by the object's instance variable even though it has never been used and technically doesn't exist.

In previous versions of Ruby, we often defined methods at runtime by calling eval. In essence, define_method can and should be used in all these circumstances. Minor subtleties like the one above shouldn't deter you.

11.3.6. Using const_missing

The const_missing method is analogous to method_missing. If you try to reference a constant that isn't known, this method will be called if it is found. A symbol referring to the constant is passed in.

To capture a constant globally, define this method within Module itself (Module is the parent of Class).

class Module   def const_missing(x)     "from Module"   end end class X end p X::BAR        # "from Module" p BAR           # "from Module" p Array::BAR    # "from Module"


You can, of course, do anything you want to give the constant a fake value, compute its value, or whatever. Remember the Roman class we saw in Chapter 6, "Symbols and Ranges"? Here we use it to ensure that arbitrary uppercase Roman numeral constants are treated as such:

class Module   def const_missing(name)     Roman.decode(name)   end end year1 = MCMLCCIV        # 1974 year2 = MMVIII          # 2008


If you want something less global, define the method as a class method on a class. That class and all its children will call that version of the method as needed.

class Alpha   def self.const_missing(sym)     "Alpha has no #{sym}"   end end class Beta   def self.const_missing(sym)     "Beta has no #{sym}"   end end class A < Alpha end class B < Beta end p Alpha::FOO       # "Alpha has no FOO" p Beta::FOO        # "Beta has no FOO" p A::FOO           # "Alpha has no FOO" p B::FOO           # "Beta has no FOO"


11.3.7. Removing Definitions

The dynamic nature of Ruby means that pretty much anything that can be defined can also be undefined. One conceivable reason to do this is to decouple pieces of code that are in the same scope by getting rid of variables after they have been used; another reason might be to specifically disallow certain dangerous method calls. Whatever your reason for removing a definition, it should naturally be done with caution because it can conceivably lead to debugging problems.

The radical way to undefine something is with the undef keyword (not surprisingly the opposite of def). You can undef methods, local variables, and constants at the top level. Although a class name is a constant, you cannot remove a class definition this way.

def asbestos   puts "Now fireproof" end tax = 0.08 PI = 3 asbestos puts "PI=#{PI}, tax=#{tax}" undef asbestos undef tax undef PI # Any reference to the above three # would now give an error.


Within a class definition, a method or constant can be undefined in the same context in which it was defined. You can't undef within a method definition or undef an instance variable.

The remove_method and undef_method methods also are available (defined in Module). The difference is subtle: remove_method will remove the current (or nearest) definition of the method; undef_method will literally cause the method to be undefined (removing it from superclasses as well). Listing 11.16 is an illustration of this.

Listing 11.16. Removing and Undefining Methods

class Parent   def alpha     puts "parent alpha"   end   def beta     puts "parent beta"   end end class Child < Parent   def alpha     puts "child alpha"   end   def beta     puts "child beta"   end   remove_method :alpha   # Remove "this" alpha   undef_method :beta     # Remove every beta end x = Child.new x.alpha          # parent alpha x.beta           # Error!

The remove_const method will remove a constant.

module Math   remove_const :PI end # No PI anymore!


Note that it is possible to remove a class definition in this way (because a class identifier is simply a constant). The following code demonstrates this:

class BriefCandle   #... end out_out = BriefCandle.new class Object   remove_const :BriefCandle end # Can't instantiate BriefCandle again! # (Though out_out still exists...)


Note that methods such as remove_const and remove_method are (naturally enough) private methods. That is why we show them being called from inside a class or module definition rather than outside.

11.3.8. Obtaining Lists of Defined Entities

The reflection API of Ruby enables us to examine the classes and objects in our environment at runtime. We'll look at methods defined in Module, Class, and Object.

The Module module has a method named constants that returns an array of all the constants in the system (including class and module names). The nesting method returns an array of all the modules nested at the current location in the code.

The instance method Module#ancestors returns an array of all the ancestors of the specified class or module.

list = Array.ancestors # [Array, Enumerable, Object, Kernel]


The constants method lists all the constants accessible in the specified module. Any ancestor modules are included.

list = Math.constants    # ["E", "PI"]


The class_variables method returns a list of all class variables in the given class and its superclasses. The included_modules method lists the modules included in a class.

class Parent   @@var1 = nil end class Child < Parent   @@var2 = nil end list1 = Parent.class_variables   # ["@@var1"] list2 = Array.included_modules   # [Enumerable, Kernel]


The Class methods instance_methods and public_instance_methods are synonyms; they return a list of the public instance methods for a class. The methods private_instance_methods and protected_instance_methods behave as expected. Any of these can take a Boolean parameter, which defaults to true; if it is set to false, superclasses will not be searched, resulting in a smaller list.

n1 = Array.instance_methods.size                 # 121 n2 = Array.public_instance_methods.size          # 121 n3 = Array.private_instance_methods.size         # 71 n4 = Array.protected_instance_methods.size       # 0 n5 = Array.public_instance_methods(falsee).size  # 71


The Object class has a number of similar methods that operate on instances (see Listing 11.17). Calling methods will return a list of all methods that can be invoked on that object. Calling public_methods will return a list of publicly accessible methods; this takes a parameter, defaulting to true, to choose whether methods from superclasses are included. The methods private_methods, protected_methods, and singleton_methods all take a similar parameter.

Listing 11.17. Reflection and Instance Variables

class SomeClass   def initialize     @a = 1     @b = 2   end   def mymeth     #...   end   protected :mymeth end x = SomeClass.new def x.newmeth   # ... end iv = x.instance_variables        # ["@b", "@a"] p x.methods.size                   # 42 p x.public_methods.size            # 41 p x.public_methods(false).size     # 1 p x.private_methods.size           # 71 p x.private_methods(false).size    # 1 p x.protected_methods.size         # 1 p x.singleton_methods.size         # 1

If you have been using Ruby for a few years, you will notice that these methods have changed a little. Default parameters are now true rather than false.

11.3.9. Examining the Call Stack

And you may ask yourself:

Well, how did I get here?

Talking Heads, "Once in a Lifetime"

Sometimes we want to know who our caller was. This could be useful information if, for example, we had a fatal exception. The caller method, defined in Kernel, makes this possible. It returns an array of strings in which the first element represents the caller, the next element represents the caller's caller, and so on.

def func1   puts caller[0]  end def func2   func1 end func2              # Prints: somefile.rb:6:in `func2'


The string is in the form file;line or file:line: in method, as shown previously.

11.3.10. Monitoring Execution of a Program

A Ruby program can introspect or examine its own execution. There are many applications for such a capability; the interested reader can refer to the sources debug.rb, profile.rb, and tracer.rb. It is even conceivable to use this facility in creating a design-by-contract (DBC) library (although the most popular one at the time of this writing doesn't use this technique).

The interesting thing is that this trick is implemented purely in Ruby. We use the Ruby method set_trace_func, which allows you to invoke a block whenever significant events happen in the execution of a program. A good reference will show the calling sequence for set_trace_func, so we'll just show a simple example here:

def meth(n)   sum = 0   for i in 1..n     sum += i   end   sum end set_trace_func(proc do |event, file, line,                         id, binding, klass, *rest|   printf "%8s %s:%d  %s/%s\n", event, file, line,                                klass, id end) meth(2)


Notice that this code follows the common convention of using do and end for a multi-line block. Because of the way Ruby is parsed, this makes the parentheses necessary. An alternative, of course, would be to use braces.

The preceding code produces the following output:

    line prog.rb:13  false/     call prog.rb:1  Object/meth     line prog.rb:2  Object/meth     line prog.rb:3  Object/meth   c-call prog.rb:3  Range/each     line prog.rb:4  Object/meth   c-call prog.rb:4  Fixnum/+ c-return prog.rb:4  Fixnum/+     line prog.rb:4  Object/meth   c-call prog.rb:4  Fixnum/+ c-return prog.rb:4  Fixnum/+ c-return prog.rb:4  Range/each     line prog.rb:6  Object/meth   return prog.rb:6  Object/meth


Another related method is Kernel#trace_var, which invokes a block whenever a global variable is assigned a value.

Suppose that you want to trace the execution of a program from outside, strictly as an aid in debugging. The simplest way to see what a program is doing is to use the tracer library mentioned previously. Given a program prog.rb:

def meth(n)   (1..n).each {|i| puts i} end meth(3)


You can simply load TRacer from the command line:

% ruby -r tracer prog.rb #0:prog.rb:1::-:     def meth(n) #0:prog.rb:1:Module:>:     def meth(n) #0:prog.rb:1:Module:<:     def meth(n) #0:prog.rb:8::-:     meth(2) #0:prog.rb:1:Object:>:     def meth(n) #0:prog.rb:2:Object:-:       sum = 0 #0:prog.rb:3:Object:-:       for i in 1..n #0:prog.rb:3:Range:>:       for i in 1..n #0:prog.rb:4:Object:-:         sum += i #0:prog.rb:4:Fixnum:>:         sum += i #0:prog.rb:4:Fixnum:<:         sum += i #0:prog.rb:4:Object:-:         sum += i #0:prog.rb:4:Fixnum:>:         sum += i #0:prog.rb:4:Fixnum:<:         sum += i #0:prog.rb:4:Range:<:         sum += i #0:prog.rb:6:Object:-:       sum #0:prog.rb:6:Object:<:       sum


The lines output by TRacer show the thread number, the filename and line number, the class being used, the event type, and the line from the source file being executed. The event types include '-' when a source line is executed, '>' for a call, '<' for a return, 'C' for a class, and 'E' for an end. (If you are automatically including a library via RUBYOPT or some other way, you may actually get thousands of lines of output instead.)

11.3.11. Traversing the Object Space

The Ruby runtime system needs to keep track of all known objects (if for no other reason than to be able to garbage collect those that are no longer referenced). This information is made accessible via the ObjectSpace.each_object method.

ObjectSpace.each_object do |obj|   printf "%20s: %s\n", obj.class, obj.inspect end


If you specify a class or module as a parameter to each_object, only objects of that type will be returned.

The ObjectSpace module is also useful in defining object finalizers (see section 11.3.14, "Defining Finalizers for Objects").

11.3.12. Handling Calls to Nonexistent Methods

Sometimes it's useful to be able to write classes that respond to arbitrary method calls. For example, you might want to wrap calls to external programs in a class, providing access to each program as a method call. You can't know ahead of time the names of all these programs, so you can't create the methods as you write the class. Here comes Object#method_missing to the rescue. Whenever a Ruby object receives a message for a method that isn't implemented in the receiver, it invokes the method_missing method instead. You can use that to catch what would otherwise be an error, treating it as a normal method call. Let's implement the operating system command wrapper class:

class CommandWrapper   def method_missing(method, *args)     system(method.to_s, *args)   end end cw = CommandWrapper.new cw.date                   # Sat Apr 28 22:50:11 CDT 2001 cw.du '-s', '/tmp'        # 166749  /tmp


The first parameter to method_missing is the name of the method that was called (and that couldn't be found). Whatever was passed in that method call is then given as the remaining parameters.

If your method_missing handler decides that it doesn't want to handle a particular call, it should call super rather than raising an exception. That allows method_missing handlers in superclasses to have a shot at dealing with the situation. Eventually, the method_missing method defined in class Object will be invoked, and that will raise an exception.

11.3.13. Tracking Changes to a Class or Object Definition

Perhaps we should start this by asking: Who cares? Why are we interested in tracking changes to classes?

One possible reason is that we're trying to keep track of the state of the Ruby program being run. Perhaps we're implementing some kind of GUI-based debugger, and we need to refresh a list of methods if our user adds one on the fly.

Another reason might be that we're doing clever things to other classes. For example, say that we wanted to write a module that could be included in any class definition. From then on, any call to a method in that class will be traced. We might use it something like this:

class MyClass   include Tracing   def one   end   def two(x, y)   end end m = MyClass.new m.one                 # one called. Params = m.two(1, 'cat')       # two called. Params = 1, cat


It will also work for any subclasses of the class we're tracing:

class Fred < MyClass   def meth(*a)   end end Fred.new.meth(2,3,4,5)   # meth called. Params = 2, 3, 4, 5


We could implement this module as shown in Listing 11.18.

Listing 11.18. Tracing Module

     module Tracing        def Tracing.included(into)          into.instance_methods(false).each { |m| Tracing.hook_method(into, m) }          def into.method_added(meth)            unless @adding              @adding = true              Tracing.hook_method(self, meth)              @adding = false            end          end        end        def Tracing.hook_method(klass, meth)          klass.class_eval do            alias_method "old_#{meth}", "#{meth}"            define_method(meth) do |*args|              puts "#{meth} called. Params = #{args.join(', ')}"              self.send("old_#{meth}",*args)            end          end        end      end      class MyClass        include Tracing        def first_meth      end      def second_meth(x, y)      end    end    m = MyClass.new    m.first_meth                 # first_meth called. Params =    m.second_meth(1, 'cat')      # second_meth called. Params = 1, cat

This code has two main methods. The first, included, is a callback invoked whenever a module is inserted into a class. Our version does two things. It calls hook_method for every method that's already been defined in the target class, and it inserts a definition for method_added into that class. This means that any subsequently added method will also be detected and hooked.

The hook itself is pretty straightforward: When a method is added, it is immediately aliased to the name old_name. The original method is then replaced by our tracing code, which dumps out the method name and parameters before invoking the original method.

Note the use of alias_method here. This works much like alias, except that it works only on methods (and it itself is a method, not a keyword). It could have been written other ways also:

# Two other ways to write that line... # Symbols with interpolation: alias_method :"old_#{meth}", :"#{meth}" # Strings converted via to_sym: alias_method "old_#{meth}".to_sym, meth.to_sym


To detect the addition of a new class method to a class or module, we can define a class method singleton_method_added within that class. (Recall that a singleton method in this sense is what we usually refer to as a class methodbecause Class is an object.) This method comes from Kernel and by default does nothing, but we can make it behave as we want.

class MyClass   def MyClass.singleton_method_added(sym)     puts "Added method #{sym.to_s} to class MyClass."   end   def MyClass.meth1     puts "I'm meth1."   end end def MyClass.meth2   puts "And I'm meth2." end


The output we get from this is as follows:

Added method singleton_method_added to class MyClass. Added method meth1 to class MyClass. Added method meth2 to class MyClass.


Note that actually three methods are added here. Perhaps contrary to expectation, singleton_method_added can track its own addition to the class.

The inherited method (from Class) is used in much the same way. It is called whenever a class is subclassed by another.

class MyClass   def MyClass.inherited(subclass)     puts "#{subclass} inherits from MyClass."   end   # ... end class OtherClass < MyClass   # ... end # Output: OtherClass inherits from MyClass.


We can also track the addition of a module's instance methods to an object (done via the extend method). The method extend_object is called whenever an extend is done.

module MyMod   def MyMod.extend_object(obj)     puts "Extending object id #{obj.object_id}, class #{obj.class}"     super   end   # ... end x = [1, 2, 3] x.extend(MyMod) # Output: # Extending object id 36491192, type Array


Note that the call to super is needed for the real extend_object method to do its work. This is analogous to the behavior of append_features (see section 11.1.12, "Working with Modules"); this method can also be used to track the use of modules.

11.3.14. Defining Finalizers for Objects

Ruby classes have constructors (the methods new and initialize) but don't have destructors (methods that delete objects). That's because Ruby uses mark-and-sweep garbage collection to remove unreferenced objects; a destructor would make no sense.

However, people coming to Ruby from languages such as C++ seem to miss the facility and often ask how they can write code to handle the finalization of objects. The simple answer is that there is no real way to do it reliably. But you can arrange to have code called when an object is garbage collected.

a = "hello" puts "The string 'hello' has an object id #{a.id}" ObjectSpace.define_finalizer(a) { |id| puts "Destroying #{id}" } puts "Nothing to tidy" GC.start a = nil puts "The original string is now a candidate for collection" GC.start


This produces the following output:

The string 'hello' has an object id 537684890 Nothing to tidy The original string is now a candidate for collection Destroying 537684890


Note that by the time the finalizer is called, the object has basically been destroyed already. An attempt to convert the ID you receive back into an object reference using ObjectSpace._id2ref will raise a RangeError, complaining that you are attempting to use a recycled object.

Also, be aware that Ruby uses a conservative mark-and-sweep GC mechanism. There is no guarantee that an object will undergo garbage collection before the program terminates.

However, all this might be moot. There's a style of programming in Ruby that uses blocks to encapsulate the use of a resource. At the end of the block, the resource is deleted, and life carries on merrily, all without the use of finalizers. For example, consider the block form of File.open:

File.open("myfile.txt") do |file|   line1 = file.read   # ... end


Here the File object is passed into the block, and when the block exits, the file is closed, all under control of the open method. If you wanted to write a subset of File.open in Ruby (for efficiency it's currently written in C as part of the runtime system), it might look something like this:

def File.open(name, mode = "r")   f = os_file_open(name, mode)   if block_given?     begin       yield f     ensure       f.close     end     return nil   else     return f   end end


The preceding routine tests for the presence of a block. If found, it invokes that block, passing in the open file. It does this in the context of a begin-end block, ensuring that it will close the file after the block terminates, even if an exception is thrown.




The Ruby Way(c) Solutions and Techniques in Ruby Programming
The Ruby Way, Second Edition: Solutions and Techniques in Ruby Programming (2nd Edition)
ISBN: 0672328844
EAN: 2147483647
Year: 2004
Pages: 269
Authors: Hal Fulton

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net