Reference page
Getting Started with miniextendr
This guide walks you through creating your first R package with a Rust backend using miniextendr.
This guide walks you through creating your first R package with a Rust backend using miniextendr.
πPrerequisites
- Rust (1.85+): Install from rustup.rs
- R (4.0+): Install from CRAN
- R development tools:
install.packages("devtools")
Verify your setup:
rustc --version # Should be 1.85+
R --version # Should be 4.0+πQuick Start
πStep 1: Create a New Package
Use the minirextendr helper package to scaffold a new project:
# Install minirextendr (once)
install.packages("minirextendr") # or: devtools::install_github("...")
# Create a new package
library(minirextendr)
create_miniextendr_package("mypackage")
This creates a package structure with:
mypackage/
βββ DESCRIPTION
βββ NAMESPACE
βββ R/
β βββ mypackage_wrappers.R # Auto-generated R wrappers
βββ src/
β βββ rust/
β βββ Cargo.toml
β βββ lib.rs # Your Rust code goes here
β βββ vendor/ # Vendored miniextendr crates
βββ configure # Build configuration script
βββ configure.ac # Autoconf sourceπStep 2: Write Your First Function
Edit src/rust/lib.rs:
use miniextendr_api::miniextendr;
/// Add two integers.
/// @param a First number
/// @param b Second number
/// @return The sum of a and b
#[miniextendr]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
/// Greet someone by name.
/// @param name The name to greet
/// @return A greeting string
#[miniextendr]
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
// Registration is automatic via #[miniextendr].πStep 3: Build and Test
# Recommended: devtools handles everything in one step
# (compiles Rust, generates R wrappers, runs roxygen2)
devtools::document("mypackage")
devtools::install("mypackage")
Or manually without devtools:
cd mypackage
./configure # Generate build files
R CMD INSTALL . # Compile Rust and installπStep 4: Use from R
library(mypackage)
add(1L, 2L)
# [1] 3
greet("World")
# [1] "Hello, World!"
πCore Concepts
πThe #[miniextendr] Attribute
Mark functions for export to R:
#[miniextendr]
pub fn my_function(x: i32) -> i32 {
x * 2
}
The macro:
- Generates a C wrapper callable from R
- Handles type conversion (R β Rust)
- Manages error handling and panics
- Extracts documentation from Rust doc comments
πAutomatic Registration
Items annotated with #[miniextendr] are automatically registered via linkme distributed slices β no manual module declarations needed.
πType Conversions
miniextendr automatically converts between R and Rust types:
| R Type | Rust Type |
|---|---|
| integer | i32 |
| numeric | f64 |
| character | String, &str |
| logical | bool |
| integer vector | Vec<i32>, &[i32] |
| numeric vector | Vec<f64>, &[f64] |
| list | Various (see below) |
| NULL | () |
| NA | Option<T> (None = NA) |
πCreating Classes
miniextendr supports multiple R class systems. Hereβs a quick comparison:
πEnvironment Style (Default)
Simple method dispatch via $:
#[derive(miniextendr_api::ExternalPtr)]
pub struct Counter { value: i32 }
#[miniextendr] // Default: env style
impl Counter {
pub fn new(initial: i32) -> Self {
Counter { value: initial }
}
pub fn value(&self) -> i32 {
self.value
}
pub fn increment(&mut self) {
self.value += 1;
}
}c <- Counter$new(0L)
c$value() # 0
c$increment()
c$value() # 1πR6 Style
Full R6 class with encapsulation:
#[miniextendr(r6)]
impl Counter {
// ... same methods
// Active binding (property-like access)
#[miniextendr(r6(active))]
pub fn current(&self) -> i32 {
self.value
}
}c <- Counter$new(0L)
c$value() # Method call
c$current # Active binding (no parens)πS3, S4, S7
#[miniextendr(s3)] // S3 generic functions
#[miniextendr(s4)] // S4 setClass/setMethod
#[miniextendr(s7)] // S7 new_class
impl Counter { ... }
See CLASS_SYSTEMS.md for detailed comparison.
πError Handling
πPanics
Rust panics are converted to R errors:
#[miniextendr]
pub fn divide(a: f64, b: f64) -> f64 {
if b == 0.0 {
panic!("Division by zero");
}
a / b
}divide(1, 0)
# Error: Division by zeroπResult Types
Return Result<T, E> for structured error handling:
#[miniextendr]
pub fn parse_int(s: &str) -> Result<i32, String> {
s.parse().map_err(|e| format!("Parse error: {}", e))
}
By default, Err values cause R errors. Use #[miniextendr(unwrap_in_r)] to return errors as R values:
#[miniextendr(unwrap_in_r)]
pub fn try_parse(s: &str) -> Result<i32, String> {
s.parse().map_err(|e| e.to_string())
}try_parse("42") # 42
try_parse("abc") # list(error = "invalid digit...")
πWorking with Vectors
πSlices (Zero-Copy)
For read-only access, use slices:
#[miniextendr]
pub fn sum_slice(x: &[f64]) -> f64 {
x.iter().sum()
}
This provides zero-copy access to Rβs vector data.
πOwned Vectors
For modification, use Vec<T>:
#[miniextendr]
pub fn double_values(x: Vec<i32>) -> Vec<i32> {
x.into_iter().map(|v| v * 2).collect()
}πNA Handling
Use Option<T> to handle NA values:
#[miniextendr]
pub fn replace_na(x: Vec<Option<f64>>, replacement: f64) -> Vec<f64> {
x.into_iter()
.map(|v| v.unwrap_or(replacement))
.collect()
}
πOpaque Pointers (ExternalPtr)
For complex Rust types that donβt map to R types:
#[derive(miniextendr_api::ExternalPtr)]
pub struct Database {
connection: Connection,
}
#[miniextendr]
impl Database {
pub fn new(path: &str) -> Self {
Database { connection: Connection::open(path).unwrap() }
}
pub fn query(&self, sql: &str) -> Vec<String> {
// ...
}
}
The ExternalPtr derive:
- Wraps the Rust struct in Rβs external pointer type
- Automatically runs
Dropwhen R garbage collects - Provides type-safe access across function calls
πDevelopment Workflow
πIteration Cycle
- Edit Rust code in
src/rust/lib.rs - Run
devtools::document()β compiles Rust, generates R wrappers, runs roxygen2 - Run
devtools::install()β install the package - Test in R
devtools::document() handles ./configure, compilation, and wrapper generation
automatically via bootstrap.R and the Makevars dependency chain. No manual
./configure or two-pass install needed.
πDebugging Tips
- Rust panics: Set
MINIEXTENDR_BACKTRACE=1for full backtraces - Compilation errors: Check
src/rust/Cargo.tomldependencies - R errors: Check that functions have
#[miniextendr]and arepub
πCommon Patterns
πDefault Parameters
/// @param amount Amount to add (default: 1)
#[miniextendr]
pub fn increment(value: i32, #[miniextendr(default = "1")] amount: i32) -> i32 {
value + amount
}increment(5) # 6 (uses default)
increment(5, 3) # 8πVariadic Arguments (Dots)
use miniextendr_api::dots::Dots;
#[miniextendr]
pub fn count_args(_dots: &Dots, ...) -> i32 {
_dots.len() as i32
}count_args(1, 2, 3, "a", "b") # 5πFactors (Enums)
use miniextendr_api::RFactor;
#[derive(RFactor)]
pub enum Color { Red, Green, Blue }
#[miniextendr]
pub fn describe_color(color: Color) -> &'static str {
match color {
Color::Red => "warm",
Color::Green => "cool",
Color::Blue => "cool",
}
}describe_color(factor("Red", levels = c("Red", "Green", "Blue")))
# [1] "warm"
πNext Steps
- CLASS_SYSTEMS.md - Detailed class system comparison
- ALTREP.md - Lazy/compact vectors
- THREADS.md - Threading and parallelism
- SAFETY.md - Memory safety guarantees
πTroubleshooting
πβconfigure: command not foundβ
Run autoconf first:
cd mypackage && autoconf && ./configureπβcould not find functionβ in R
Ensure the function is:
- Marked
pub - Has
#[miniextendr]attribute
Then rebuild: ./configure && R CMD INSTALL .
πCompilation Errors
Check src/rust/Cargo.toml for dependency issues. Run:
cd src/rust && cargo checkπNext Steps
- Documentation Index β Browse all available documentation
- Known Gaps & Limitations β Important context on whatβs missing or limited
- Troubleshooting β Common issues and solutions
- Architecture Overview β How miniextendr works under the hood