Reference page
S3 Methods Guide
How to implement S3 generics (print, format, etc.) with #[miniextendr(s3)].
How to implement S3 generics (print, format, etc.) with #[miniextendr(s3)].
πQuick Example
use miniextendr_api::{miniextendr, ExternalPtr};
#[derive(ExternalPtr)]
pub struct Person {
name: String,
age: i32,
}
#[miniextendr(s3)]
impl Person {
/// @param name Name of the person.
/// @param age Age of the person.
pub fn new(name: String, age: i32) -> Self {
Person { name, age }
}
/// Implements format.Person β returns a formatted string.
#[miniextendr(generic = "format")]
pub fn fmt(&self) -> String {
format!("{} (age {})", self.name, self.age)
}
/// Implements print.Person β prints and returns self invisibly.
#[miniextendr(generic = "print")]
pub fn show(&mut self) {
println!("Person: {}, age {}", self.name, self.age);
}
/// Custom method: greet.Person
pub fn greet(&self) -> String {
format!("Hello, I'm {}!", self.name)
}
}
// Registration is automatic via #[miniextendr].
Generated R code:
# Constructor
new_person <- function(name, age) {
structure(.Call(C_Person__new, name, age), class = "Person")
}
# format.Person β returns the string directly
format.Person <- function(x, ...) {
.Call(C_Person__fmt, x)
}
# print.Person β calls Rust, then returns invisible(x)
print.Person <- function(x, ...) {
.Call(C_Person__show, x)
invisible(x)
}
# greet generic + method
greet <- function(x, ...) UseMethod("greet")
greet.Person <- function(x, ...) {
.Call(C_Person__greet, x)
}πKey Concepts
πGeneric Override with #[miniextendr(generic = "...")]
By default, the Rust method name becomes the S3 generic name. Use generic = "..." to
override this, mapping to a different R generic:
// Rust method is `fmt`, but R generic is `format`
#[miniextendr(generic = "format")]
pub fn fmt(&self) -> String { ... }
// Rust method is `show`, but R generic is `print`
#[miniextendr(generic = "print")]
pub fn show(&mut self) { ... }
This is essential for base R generics like print and format where you want your Rust
method to have a more descriptive name.
πDots (...)
All S3 method signatures include ... automatically. You donβt need to declare them
in your Rust signature β the generated R wrapper adds ... to the parameter list:
# Generated: always has (x, ...) or (x, other_params, ...)
format.Person <- function(x, ...) { ... }
greet.Person <- function(x, ...) { ... }
If your Rust method takes extra parameters, they appear between x and ...:
pub fn describe(&self, verbose: bool) -> String { ... }
// β describe.Person <- function(x, verbose, ...) { ... }πConstructor
The constructor is always the method returning Self. It generates a function named
new_<classname_lowercase>() that wraps the result with structure(..., class = "ClassName"):
pub fn new(name: String, age: i32) -> Self { ... }
// β new_person <- function(name, age) {
// structure(.Call(C_Person__new, name, age), class = "Person")
// }πStatic Methods
Methods without self become standalone functions prefixed with the lowercase class name:
pub fn species() -> String { "Homo sapiens".into() }
// β person_species <- function() { .Call(C_Person__species) }πImplementing print
R convention: print() displays output and returns invisible(x) so the object
can be used in pipelines without double-printing.
Recommended pattern β use &mut self returning ():
#[miniextendr(generic = "print")]
pub fn show(&mut self) {
println!("Person: {}, age {}", self.name, self.age);
}
This generates the ChainableMutation return strategy:
print.Person <- function(x, ...) {
.Call(C_Person__show, x)
invisible(x)
}
The &mut self + void return triggers invisible(x) after the .Call(). This
matches the R convention where print() returns the object invisibly.
Why &mut self? The invisible(x) pattern is only generated for &mut self
methods returning (). With &self returning (), the generated code would be
a bare .Call(...) returning NULL β functional but doesnβt follow R convention.
If your print method doesnβt actually mutate, using &mut self is a pragmatic
choice to get correct R behavior.
Alternative β &self returning ():
#[miniextendr(generic = "print")]
pub fn show(&self) {
println!("Person: {}, age {}", self.name, self.age);
}
Generates:
print.Person <- function(x, ...) {
.Call(C_Person__show, x)
}
This works β the println! output appears β but the function returns NULL instead
of invisible(x). For most interactive use this is fine, but y <- print(x) gives
NULL rather than x.
πImplementing format
R convention: format() returns a character vector representation. Many R functions
use format() internally (e.g., paste(), cat(format(x))).
Recommended pattern β &self returning String:
#[miniextendr(generic = "format")]
pub fn fmt(&self) -> String {
format!("{} (age {})", self.name, self.age)
}
Generates:
format.Person <- function(x, ...) {
.Call(C_Person__fmt, x)
}
The string is returned directly (visible), which is correct for format().
Tip: Implement format even if you also implement print. Other R code may call
format() on your object (e.g., when building error messages or tibble displays).
πImplementing Both print and format
A complete type should implement both. The standard R pattern is for print to call
format internally, but with miniextendr each method dispatches to separate Rust code:
#[derive(ExternalPtr)]
pub struct Temperature {
celsius: f64,
}
#[miniextendr(s3)]
impl Temperature {
pub fn new(celsius: f64) -> Self {
Temperature { celsius }
}
#[miniextendr(generic = "format")]
pub fn fmt(&self) -> String {
format!("{:.1}Β°C", self.celsius)
}
#[miniextendr(generic = "print")]
pub fn show(&mut self) {
println!("{:.1}Β°C", self.celsius);
}
}
Usage in R:
t <- new_temperature(36.6)
print(t) # 36.6Β°C (returns invisible(t))
format(t) # "36.6Β°C" (returns the string)
cat(format(t), "\n") # 36.6Β°CπSummary Table
| Method | Receiver | Return | Generated R | R Convention |
|---|---|---|---|---|
format | &self | String | .Call(...) | Returns formatted string (visible) |
print | &mut self | () | .Call(...); invisible(x) | Returns self invisibly |
print | &self | () | .Call(...) | Returns NULL (works but unconventional) |
| custom | &self | any | .Call(...) | Returns value directly |
| custom | &mut self | () | .Call(...); invisible(x) | Chainable mutation |
πSee Also
- CLASS_SYSTEMS.md β Overview of all 6 class systems (env, R6, S3, S4, S7, vctrs)
- DOTS_TYPED_LIST.md β Using dots and typed_list validation
- VCTRS.md β vctrs-compatible S3 classes with
formatmethods