The R6 package provides an implementation of encapsulated object-oriented programming for R (also sometimes referred to as classical object-oriented programming). It is similar to R’s reference classes, but it is more efficient and doesn’t depend on S4 classes and the methods package.

R6 classes

R6 classes are similar to R’s reference classes, but are lighter weight, and avoid some issues that come along with using S4 (R’s reference classes are based on S4). For more information about speed and memory footprint, see the Performance article.

Unlike many objects in R, instances (objects) of R6 classes have reference semantics. R6 classes also support:

  • public and private methods
  • active bindings
  • inheritance (superclasses) which works across packages

Why the name R6? When R’s reference classes were introduced, some users, following the names of R’s existing class systems S3 and S4, called the new class system R5 in jest. Although reference classes are not actually called R5, the name of this package and its classes takes inspiration from that name.

The name R5 was also a code-name used for a different object system started by Simon Urbanek, meant to solve some issues with S4 relating to syntax and performance. However, the R5 branch was shelved after a little development, and it was never released.

Basics

Here’s how to create a simple R6 class. The public argument is a list of items, which can be functions and fields (non-functions). Functions will be used as methods.

To instantiate an object of this class, use $new():

The $new() method creates the object and calls the initialize() method, if it exists.

Inside methods of the class, self refers to the object. Public members of the object (all you’ve seen so far) are accessed with self$x, and assignment is done with self$x <- y.

Once the object is instantiated, you can access values and methods with $:

Implementation note: The external face of an R6 object is basically an environment with the public members in it. This is also known as the public environment. An R6 object’s methods have a separate enclosing environment which, roughly speaking, is the environment they “run in”. This is where self binding is found, and it is simply a reference back to public environment.

Private members

In the previous example, all the members were public. It’s also possible to add private members:

Whereas public members are accessed with self, like self$add(), private members are accessed with private, like private$queue.

The public members can be accessed as usual:

However, private members can’t be accessed directly:

A useful design pattern is for methods to return self (invisibly) when possible, because it makes them chainable. For example, the add() method returns self so you can chain them together:

On the other hand, remove() returns the value removed, so it’s not chainable:

Active bindings

Active bindings look like fields, but each time they are accessed, they call a function. They are always publicly visible.

When an active binding is accessed as if reading a value, it calls the function with value as a missing argument:

When it’s accessed as if assigning a value, it uses the assignment value as the value argument:

If the function takes no arguments, it’s not possible to use it with <-:

Implementation note: Active bindings are bound in the public environment. The enclosing environment for these functions is also the public environment.

Inheritance

One R6 class can inherit from another. In other words, you can have super- and sub-classes.

Subclasses can have additional methods, and they can also have methods that override the superclass methods. In this example of a queue that retains its history, we’ll add a show() method and override the remove() method:

Superclass methods can be called with super$xx(). The CountingQueue (example below) keeps a count of the total number of objects that have ever been added to the queue. It does this by overriding the add() method – it increments a counter and then calls the superclass’s add() method, with super$add(x):

Fields containing reference objects

If your R6 class contains any fields that also have reference semantics (e.g., other R6 objects, and environments), those fields should be populated in the initialize method. If the field set to the reference object directly in the class definition, that object will be shared across all instances of the R6 objects. Here’s an example:

To avoid this, populate the field in the initialize method:

Other topics

Adding members to an existing class

It is sometimes useful to add members to a class after the class has already been created. This can be done using the $set() method on the generator object.

The new members will be present only in instances that are created after $set() has been called.

To prevent modification of a class, you can use lock_class=TRUE when creating the class. You can also lock and unlock a class as follows:

Cloning objects

By default, R6 objects have method named clone for making a copy of the object.

If you don’t want a clone method to be added, you can use cloneable=FALSE when creating the class. If any loaded R6 object has a clone method, that function uses 75.1 kB, but for each additional object, the clone method costs a trivial amount of space (112 bytes).

Deep cloning

If there are any fields which are objects with reference sematics (environments, R6 objects, reference class objects), the copy will get a reference to the same object. This is sometimes desirable, but often it is not.

For example, we’ll create an object c1 which contains another R6 object, s, and then clone it. Because the original’s and the clone’s s fields both refer to the same object, modifying it from one results in a change that is reflect in the other.

To make it so the clone receives a copy of s, we can use the deep=TRUE option:

The default behavior of clone(deep=TRUE) is to copy fields which are R6 objects, but not copy fields which are environments, reference class objects, or other data structures which contain other reference-type objects (for example, a list with an R6 object).

If your R6 object contains these types of objects and you want to make a deep clone of them, you must provide your own function for deep cloning, in a private method named deep_clone. Below is an example of an R6 object with two fields, a and b, both of which which are environments, and both of which contain a value x. It also has a field v which is a regular (non-reference) value, and a private deep_clone method.

The deep_clone method is be called once for each field. It is passed the name and value of the field, and the value it returns is be used in the clone.

When c1$clone(deep=TRUE) is called, the deep_clone method is called for each field in c1, and is passed the name of the field and value. In our version, the a environment gets copied, but b does not, nor does v (but that doesn’t matter since v is not a reference object). We can test out the clone:

In the example deep_clone method above, we checked the name of each field to determine what to do with it, but we could also check the value, by using inherits(value, "R6"), or is.environment(), and so on.

Printing R6 objects to the screen

R6 objects have a default print method that lists all members of the object. If a class defines a print method, then it overrides the default one.

Finalizers

Sometimes it’s useful to run a function when the object is garbage collected. For example, you may want to make sure a file or database connection gets closed. To do this, you can define a finalize() method, which will be called with no arguments when the object is garbage collected.

Finalizers are implemented using the reg.finalizer() function, and they set onexit=TRUE, so that the finalizer will also be called when R exits. This is useful in some cases, like database connections.

Class methods vs. member functions

When an R6 class definition contains functions in the public or private sections, those functions are class methods: they can access self (as well as private and super when available). When an R6 object is cloned, the resulting object’s methods will have a self that refers to the new object. This works by changing the enclosing environment of the method in the cloned object.

In contrast to class methods, you can also add regular functions as members of an R6 object. This can be done by assigning the function to a field in the initialize method, or after the object has been instantiated. These functions are not class methods, and they will not have access to self, private, or super.

Here is a trivial class which has a method get_self() that simply returns self, as well as an empty member, fn. In this example, we’ll assign a function to fn which has the same body as get_self. However, since it’s a regular function, self will refer to something other than the R6 object:

As of R6 2.3.0, if the object is cloned, member (non-method) functions will not have their enclosing environment changed, which is what one would normally expect. It will behave this way: