views:

372

answers:

5

In Ruby, can one object destroy another?

For example:

class Creature
  def initialize
    @energy = 1
  end
  attr_accessor :energy
 end

class Crocodile < Creature
  def eat(creature)
    @energy += creature.energy
    creature = nil #this does not work
  end
end

fish = Creature.new
croc = Crocodile.new
croc.eat(fish)

After the crocodile has eaten a creature and absorbed its energy, the creature should cease to exist. But the code above doesn't destroy the creature.

I know that if I say fish = nil, the object that the varible fish refers to will be garbage collected. But saying creature = nil inside the Crocodile's eat method doesn't accomplish that.

Another way of putting it

From inside croc.eat, can I say "since the variable 'fish' was passed to me, when I'm done, I'm going to set 'fish' to nil?"

Update: problem solved

I've essentially taken the approach that Chuck suggested, with some modifications. Here was my reasoning:

  1. If there is no longer any variable pointing to an object, it will be garbage collected
  2. If, when an object is created, I add it to a hash (like 'x' => object), and don't create any other variable for it, then deleting that item from the hash results in garbage collecting the object
  3. It seems logical that a list of all creatures should be stored in the Creature class

Therefore, I did this:

  1. On the Creature class object, I created a hash and assigned it to an instance variable. We'll call it @creaturelist. (The reason I used an instance variable and not a class variable is so that any subclass of Creature can have its own list, too.)
  2. In the Initialize method, a new creature hands itself to the Creature class
  3. The Creature class adds a reference to that creature to @creaturelist and returns an ID to the creature.
  4. The creature remembers that ID in its own @id variable.
  5. If the creature dies, it calls the parent class with Creature.remove(@id), and the only reference to itself gets deleted.

Now I can do this:

class Predator < Creature
  def eat(creature)
    @energy += creature.energy
    creature.die
  end
end

fish = Creature.new
Creature.list #shows the fish
croc = Predator.new
croc.eat(fish)
Creature.list #no more fish

Of course, in this example, fish still points to that creature object, so it's not garbage collected. But eventually, creatures will be created and eat each other based on rules, so I won't be individually naming them.

+2  A: 

Nothing can be safely garbage collected until there are no references to it in any active scope.

Azeem.Butt
Right - and the example code was an (incorrect) attempt to remove the only reference to that object.
Nathan Long
If you have a legitimate need to do that then you probably shouldn't use a high-level scripting language where the pointer mechanics are hidden from you.
Azeem.Butt
A: 

You aren't "destroying an object" -- the GC does that for you. You're talking about being in a method, and reaching out into the caller's scope and changing a binding there.

If the binding was part of an object, and you passed the object in, you could reassign it (to nil) from there.

Ken
+1  A: 

croc.eat(fish) will nil out croc's reference to the Creature referenced by fish, but note the variable "fish" itself still holds a reference to that Creature, so the instance is not garbage.

edit: Think about it this way: Inside of croc, you're not getting fish, you're getting a copy of what's inside of fish. fish's value is a reference to the object you created with Creature.new. A copy of that reference is copied into the variable creature when you execute croc.eat(fish). So now both fish and creature have references to the same object.

It's like there's a balloon floating in the air, with two strings tied to it. fish is holding one string, and creature is holding another string. when you set creature to nil, it releases its hold on the balloon, but fish is still holding onto the balloon by its own string, so the balloon doesn't float away to the big garbage collector in the sky.

edit 2: No, you can't (without some deep magic that it would be a very bad idea to perform), reach out to fish and wipe out its reference to the object in question.

mbarnett
From inside croc.eat, can I say "since the variable 'fish' was passed to me, when I'm done, I'm going to set 'fish' to nil?"
Nathan Long
@Nathan: You can't affect local variables in another binding without some pretty arcane and ill-advised hackery. Remember that *variables* aren't passed around, the objects stored in them are. A variable is just a reference to an object.
Chuck
A: 

One Ruby object (usually) shouldn't destroy another. It's not your job to worry about whether objects still exist or not. The one exception would be if you are using a database store - there you might care about whether the objects are deleted or not.

But in general, my answer is: you shouldn't care about this. (The other answers do make sense, though.)

Peter
+6  A: 

I think the problem is that you're thinking of the program itself as the world in which these simulated things live rather than simulating one.

fish = Creature.new
croc = Crocodile.new
$world = [fish, croc]
class Crocodile
  def eat(creature)
    @energy += creature.energy
    $world.delete creature
  end
end
croc.eat fish
world # [croc], now all by his lonesome, the last creature in the world :(

And assuming the fish variable had gone out of scope like it would in a properly structured program, that object would most likely now be garbage.

Chuck
Thanks! I updated my question with an approach based on your suggestion.
Nathan Long