R6 Classes in R: Reference Semantics & Mutable State Tutorial
R6 is R's modern OOP system with reference semantics. Unlike S3/S4 where objects are copied on modification, R6 objects are mutable -- modifying an object changes it in place, just like objects in Python or Java. This makes R6 ideal for stateful things like database connections, API clients, and game objects.
R6 is provided by the R6 package (not built into base R). It's simple to use, fast, and increasingly popular for packages that manage state.
Creating Your First R6 Class
Use R6Class() to define a class. Public fields and methods go in the public list.
library(R6)
# Define a class
Dog <- R6Class("Dog",
public = list(
name = NULL,
breed = NULL,
energy = 100,
initialize = function(name, breed) {
self$name <- name
self$breed <- breed
},
bark = function() {
cat(self$name, "says: Woof!\n")
self$energy <- self$energy - 10
},
status = function() {
cat(self$name, "(", self$breed, ") - Energy:", self$energy, "\n")
}
)
)
# Create an instance with $new()
rex <- Dog$new("Rex", "Labrador")
rex$status()
rex$bark()
rex$bark()
rex$status()
Key differences from S3/S4:
Methods use self$ to access fields and other methods
Objects are created with ClassName$new()
No @ or $ dispatch magic -- everything is explicit
Reference Semantics: The Big Difference
In S3/S4, y <- x copies the object. In R6, y <- x creates a reference -- both variables point to the same object.
library(R6)
Counter <- R6Class("Counter",
public = list(
count = 0,
increment = function() {
self$count <- self$count + 1
}
)
)
# Reference semantics: both point to the SAME object
c1 <- Counter$new()
c2 <- c1 # NOT a copy!
c1$increment()
c1$increment()
cat("c1 count:", c1$count, "\n") # 2
cat("c2 count:", c2$count, "\n") # Also 2! Same object!
# To get a real copy, use $clone()
c3 <- c1$clone()
c1$increment()
cat("\nAfter cloning and incrementing c1:\n")
cat("c1 count:", c1$count, "\n") # 3
cat("c3 count:", c3$count, "\n") # Still 2 -- independent copy
This is fundamentally different from how most R objects work. Always use $clone() when you need an independent copy.
Public vs Private
Private fields and methods are only accessible from within the class. Use private$ to access them.
Q: Why use R6 instead of S3 or S4? Use R6 when you need mutable state (objects that change in place), encapsulation (private fields), or when coming from Python/Java and want familiar OOP. R6 is also faster than R5 (Reference Classes).
Q: Is R6 part of base R? No. R6 is a CRAN package. Install it with install.packages("R6"). It has zero dependencies and is very lightweight.
Q: How do I avoid accidental reference sharing? Always use $clone() (or $clone(deep = TRUE) for nested R6 objects) when you want an independent copy. y <- x does NOT copy -- it creates a reference to the same object.