14.3 Controlling access

R6Class() has two other arguments that work similarly to public:

  • private allows you to create fields and methods that are only available from within the class, not outside of it.

  • active allows you to use accessor functions to define dynamic, or active, fields.

These are described in the following sections.

14.3.1 Privacy

With R6 you can define private fields and methods, elements that can only be accessed from within the class, not from the outside53. There are two things that you need to know to take advantage of private elements:

  • The private argument to R6Class works in the same way as the public argument: you give it a named list of methods (functions) and fields (everything else).

  • Fields and methods defined in private are available within the methods using private$ instead of self$. You cannot access private fields or methods outside of the class.

To make this concrete, we could make $age and $name fields of the Person class private. With this definition of Person we can only set $age and $name during object creation, and we cannot access their values from outside of the class.

Person <- R6Class("Person", 
  public = list(
    initialize = function(name, age = NA) {
      private$name <- name
      private$age <- age
    },
    print = function(...) {
      cat("Person: \n")
      cat("  Name: ", private$name, "\n", sep = "")
      cat("  Age:  ", private$age, "\n", sep = "")
    }
  ),
  private = list(
    age = NA,
    name = NULL
  )
)

hadley3 <- Person$new("Hadley")
hadley3
#> Person: 
#>   Name: Hadley
#>   Age:  NA
hadley3$name
#> NULL

The distinction between public and private fields is important when you create complex networks of classes, and you want to make it as clear as possible what it’s ok for others to access. Anything that’s private can be more easily refactored because you know others aren’t relying on it. Private methods tend to be less important in R compared to other programming languages because the object hierarchies in R tend to be simpler.

14.3.2 Active fields

Active fields allow you to define components that look like fields from the outside, but are defined with functions, like methods. Active fields are implemented using active bindings (Section 7.2.6). Each active binding is a function that takes a single argument: value. If the argument is missing(), the value is being retrieved; otherwise it’s being modified.

For example, you could make an active field random that returns a different value every time you access it:

Rando <- R6::R6Class("Rando", active = list(
  random = function(value) {
    if (missing(value)) {
      runif(1)  
    } else {
      stop("Can't set `$random`", call. = FALSE)
    }
  }
))
x <- Rando$new()
x$random
#> [1] 0.0808
x$random
#> [1] 0.834
x$random
#> [1] 0.601

Active fields are particularly useful in conjunction with private fields, because they make it possible to implement components that look like fields from the outside but provide additional checks. For example, we can use them to make a read-only age field, and to ensure that name is a length 1 character vector.

Person <- R6Class("Person", 
  private = list(
    .age = NA,
    .name = NULL
  ),
  active = list(
    age = function(value) {
      if (missing(value)) {
        private$.age
      } else {
        stop("`$age` is read only", call. = FALSE)
      }
    },
    name = function(value) {
      if (missing(value)) {
        private$.name
      } else {
        stopifnot(is.character(value), length(value) == 1)
        private$.name <- value
        self
      }
    }
  ),
  public = list(
    initialize = function(name, age = NA) {
      private$.name <- name
      private$.age <- age
    }
  )
)

hadley4 <- Person$new("Hadley", age = 38)
hadley4$name
#> [1] "Hadley"
hadley4$name <- 10
#> Error in (function (value) : is.character(value) is not TRUE
hadley4$age <- 20
#> Error: `$age` is read only

14.3.3 Exercises

  1. Create a bank account class that prevents you from directly setting the account balance, but you can still withdraw from and deposit to. Throw an error if you attempt to go into overdraft.

  2. Create a class with a write-only $password field. It should have $check_password(password) method that returns TRUE or FALSE, but there should be no way to view the complete password.

  3. Extend the Rando class with another active binding that allows you to access the previous random value. Ensure that active binding is the only way to access the value.

  4. Can subclasses access private fields/methods from their parent? Perform an experiment to find out.


  1. Because R is such a flexible language, it’s technically still possible to access private values, but you’ll have to try much harder, spelunking in to the details of R6’s implementation.↩︎