Skip to main content

Squirrel Object Management

How the language’s garbage collector keeps track of instances

When an object is created in a Squirrel program, how does the programmer ensure that the instantiated object doesn’t consume valuable memory once it is no longer needed? The short answer is that he or she doesn’t need to: Squirrel does it all for them.

Squirrel, like JavaScript but unlike C, makes use of garbage collection. This means that Squirrel continually monitors all extant objects to determine which of them are still required by the program. Any objects that are found to be no longer needed are automatically removed and the memory they occupied freed for other uses, a process called deallocation’.

Squirrel tracks a given object’s usage by maintaining a count of every variable that holds a reference to that object. When an object’s reference count falls to zero, Squirrel automatically deallocates the object — you, the programmer, need take no action.

When an object is created, its reference count is set to 1. The object’s reference count increases automatically every time a new variable is used to store a reference to that object. Adding the object to an array or placing it in a table also increases its reference count. If one of these referring variables is given a reference to a different object, or a scalar value, the object’s reference count is reduced by 1. The object’s reference count is decremented if it is removed from an array or table. Here is an example:

// Instantiate the class Test and store a reference to it
local test = Test();      // Object reference count = 1

// Store a reference to the object in a second variable
local aTest = test;       // Object reference count = 2

// Give the original referring variable a different value
test = 42;                // Object reference count = 1

// Give the second referring variable a different value
aTest = "Done";           // Object reference count = 0
                          // Squirrel deallocates object

When the variable aTest is given a new value in the last line, the object created at the start no longer has any references to it, as indicated by its zero reference count. Squirrel’s garbage collection mechanism detects this an automatically deallocates the objects.

The key advantage of garbage collection for the programmer is that he or she doesn’t need to explicitly track object usage to ensure objects are present when needed, or to deallocate objects when they are done with. As soon as there are no variables storing references to the object, all objects are automatically deallocated by Squirrel itself. For example:

function foo() {
  local test = Test();
  test.message = "A message";
  local result = test.send();
  return result;
}

// Call the function
local result = foo();

The first line of the function foo() instantiates an object of class Test and stores a reference to it in the local variable test. The code next sets a property of the object and then calls one of the object’s methods, which returns a value that is stored in the local variable result. This value is then returned by the function. At this point the function ends and test goes out of scope; the instantiated object’s reference count is decremented. Since the object’s reference count is now zero, it is deallocated by Squirrel.

Another way of describing this is that objects are deallocated when they are no longer reachable from any existing variable. If you can ‘find’ , ‘get to’ or ‘reach’ an object by searching all tables, arrays, classes, instances, generators, closures etc. starting from the root table (and from locals of any currently-executing function), then that object will stay alive — the moment that stops being the case (the moment that the object becomes unreachable), it is deallocated.

Squirrel’s garbage collection ensure that it isn’t necessary to manually deallocate an object, but you may still do so if you wish. The Squirrel methodology is simply to set the referring variable(s) to null:

function foo() {
  local test = Test();
  test.message = "A message";
  local result = test.send();
  test = null;
  return result;
}

// Call the function
local result = foo();

As we’ve seen, setting test to null causes the Test instance’s reference count to be decremented, in this case to zero which in turn causes it to be automatically deallocated. Bear in mind that deallocation only occurs because the object’s reference count fell to zero; not specifically because test was set to null. If another variable was also holding a reference to the instance of Test, it would not be deallocated:

function foo(objectRef) {
  objectRef.message = "A message";
  local result = objectRef.send();
  objectRef = null;
  return result;
}

local test = Test();

// Call the function
local result = foo(test);

Here, the object instantiated and referred to by the variable test is passed into the function — by reference, which is stored in the variable objectRef. As we saw above, this increases the object’s reference count to 2, which is then reduced to 1 when objectRef is manually set to null. As such, the object remains available for use after the function returns.

If you wish to retain the object beyond the scope in which it was created, you can simply create another variable to store a reference to it. For example:

function foo() {
  local test = Test();
  test.message = "A message";
  test.result = test.send();
  return test;
}

local aTest = foo();

Here, the function foo() returns a reference to the Test object: at this point in the code, the object’s reference count is incremented (to 2). As we’ve seen, when the function ends, Squirrel decrements the object’s reference count. This time, however, the count falls to 1, so Squirrel does not deallocate it. Therefore any subsequent attempt to call its methods or read/set its properties (via aTest) will not fail.

Squirrel is smart enough to ensure that the object isn’t deallocated by test going out of scope before a reference to it is stored in aTest.

Any variable defined as local will be automatically removed the moment it goes out of scope. If that variable is holding a reference to an object, the object’s reference count will therefore automatically decrement. To ensure that such a variable does not go out of scope while a program is running, you can set the variable as local to the main body of the program (as above), or declare it as a global variable:

function foo() {
  local test = Test();
  test.message = "A message";
  test.result = test.send();
  return test;
}

aTest <- foo();

The bottom line: if your program has a variable that points to an object, and the variable is not null you can be sure the object is available for use. You do not need to manage the object's lifecycle yourself; Squirrel does that on your behalf, just as it handles memory allocation when you create an object.

Don’t forget, this process also applies to object properties which are themselves objects: when the parent is deallocated, so (provided their own reference counts are zero) are their children. Squirrel runs through the list of properties (and properties of properties...), setting reference counts accordingly.

Finally, what about to or more objects that reference each other? For example, if object A refers to object B, and B refers to A, yet nothing in the root table refers (even indirectly) to either of them, won’t they both still have reference counts of 1 and therefore not get deallocated? Can Squirrel deal with such circular references?

Yes, it can. In addition to the the reference-counting garbage collector which takes effect instantly, Squirrel has a second, mark-and-sweep garbage collector. Thus runs whenever Squirrel goes idle, ie. it has reached the end of the main program function, and just after each event handler runs. This mark-and-sweep pass notices that A and B are unreachable from anything other than themselves and deallocates them.