| name | miniextendr-guide |
| description | Use when working in an R package with a miniextendr Rust backend and you need orientation — how the build pipeline works (configure → Makevars → cargo → wrapper generation), which files are generated vs hand-written, what the dev loop is, how to add a function or a Cargo feature, or which of the other miniextendr skills to load for a specific task. |
miniextendr package guide
This package embeds a Rust backend via the miniextendr framework. Rust
functions annotated with #[miniextendr] become callable R functions; the
scaffolding (from the minirextendr R package) wires cargo into R's build
system so R CMD INSTALL and CRAN submission work like any other package.
Two names to keep straight:
- miniextendr — the Rust framework (
miniextendr-api crate in
src/rust/Cargo.toml). Your Rust code links against it.
- minirextendr — the R helper package that scaffolded this package and
drives the dev loop (
minirextendr::miniextendr_build(),
minirextendr::miniextendr_doctor(), minirextendr::use_*()).
Package layout
DESCRIPTION, NAMESPACE # normal R package files (NAMESPACE is roxygen2-managed)
R/ # hand-written R code
R/<pkg>-wrappers.R # GENERATED R wrappers — never edit (gitignored)
src/rust/lib.rs # YOUR Rust code lives here (#[miniextendr] functions)
src/rust/Cargo.toml # the Rust crate; [features] passthroughs go here
src/rust/build.rs # runs miniextendr-lint (MXL rules) at cargo build time
src/rust/wasm_registry.rs # GENERATED wasm32 registration snapshot (gitignored)
src/Makevars.in # build template → src/Makevars (via ./configure)
src/stub.c # force-link anchor; R's build needs ≥1 C file
configure.ac → configure # install-mode detection; regen with autoconf
bootstrap.R # run by R CMD build: configure + auto-vendor
tools/ # configure-time helpers (never call minirextendr from configure)
inst/vendor.tar.xz # THE LATCH: presence flips builds to offline mode (gitignored)
Generated files (R/<pkg>-wrappers.R, src/rust/wasm_registry.rs,
src/Makevars, src/rust/.cargo/config.toml, configure) are never edited
by hand. NAMESPACE and man/ are generated by roxygen2 but tracked in git.
How the build works
bash ./configure
→ writes src/Makevars + src/rust/.cargo/config.toml (per install mode)
make (via R CMD INSTALL)
→ cargo build --lib (staticlib, codegen-units = 1)
→ link <pkg>.so (staticlib + stub.o; stub.c anchors the whole crate)
→ load <pkg>.so in R (MINIEXTENDR_WRAPPER_GEN=1, minimal init)
→ writes R/<pkg>-wrappers.R (the .Call wrappers + roxygen docs)
→ writes src/rust/wasm_registry.rs (wasm32 registration snapshot)
devtools::document()
→ roxygen2 reads the wrappers → NAMESPACE + man/
Registration is automatic: #[miniextendr] adds entries to linkme
#[distributed_slice] tables; miniextendr_init!() in lib.rs generates
R_init_<pkg> which registers everything with R at load time. stub.c +
codegen-units = 1 guarantee the linker keeps every registration entry.
Three install modes, decided by ./configure:
| Mode | Trigger | Behavior |
|---|
| dev/source | no inst/vendor.tar.xz | cargo resolves deps from the network (or git) |
| tarball | inst/vendor.tar.xz present | offline build from vendored sources; wrapper-gen skipped (pre-shipped wrappers used) |
| wasm32 | webR cross-compile | uses pre-generated wrappers + wasm_registry.rs from a prior native build; no wrapper-gen possible |
The dev loop
minirextendr::miniextendr_build()
This runs autoconf → configure → install (compiles Rust, regenerates
wrappers) → devtools::document() → reinstalls once more if NAMESPACE gained
new exports. One call, everything consistent.
Do NOT use bare R CMD INSTALL . or devtools::install() on a fresh
package. Their R CMD build step runs bootstrap.R, which vendors
dependencies and flips configure into tarball mode — tarball mode skips
wrapper generation, so library(pkg) exposes no functions.
miniextendr_build() detects and handles this (fresh-package bootstrap,
MINIEXTENDR_FORCE_WRAPPER_GEN).
Health check when anything is confusing:
minirextendr::miniextendr_doctor()
It checks the toolchain, Cargo.toml, stale vendor tarballs, missing
.cargo/config.toml, stale generated files, and NAMESPACE useDynLib.
Adding a Rust function
#[miniextendr]
pub fn sum_sq(x: &[f64]) -> f64 {
x.iter().map(|v| v * v).sum()
}
- Must be
pub and carry #[miniextendr].
- If it lives in a new file, add
mod my_module; to lib.rs — unreachable
modules are silently skipped.
- The
/// doc comment is real roxygen: @param/@return/@export flow
into the generated wrapper and man/.
- Rebuild:
minirextendr::miniextendr_build().
For quick experiments without a package rebuild, compile inline:
minirextendr::rust_function('
#[miniextendr]
fn double_it(x: f64) -> f64 { x * 2.0 }
')
double_it(21)
Cargo features
src/rust/Cargo.toml exposes framework features as pass-throughs, e.g.:
[features]
default = []
nonapi = ["miniextendr-api/nonapi"]
connections = ["miniextendr-api/connections"]
Add integrations the same way (rayon = ["miniextendr-api/rayon"], serde,
uuid, time, …) or use the minirextendr::use_*() helpers (use_rayon(),
use_serde(), use_r6(), use_s4(), use_s7(), use_vctrs()), which also
handle R-side DESCRIPTION changes. Configure-time feature detection
(features switched on per-machine at install time) is managed by
minirextendr::use_feature_detection() / update_feature_detection() and
lives in tools/detect-features.R; configure passes the result to cargo via
--features=$CARGO_FEATURES.
Rules of the road
bash ./configure, never bare ./configure (the #!/bin/sh shebang
produces spurious errors in the config-command passthrough).
- Never edit generated files — change the source (
lib.rs, Makevars.in,
configure.ac) and rebuild.
- configure never mutates sources: helpers that configure needs belong in
tools/*.R, invoked as Rscript tools/foo.R, and must not call
minirextendr::* (the library may not be installed at configure time).
- Lint failures (MXL###) are bugs to fix, not silence — see the
miniextendr-debugging skill for the common rules.
- Check the built tarball, not the source dir before release:
R CMD check <pkg>_*.tar.gz --as-cran.
Which skill to load next
| Task | Skill |
|---|
| Build fails, function missing in R, segfault, GC bug, lint error | miniextendr-debugging |
| R ↔ Rust type mapping, NA handling, Option, vectors | miniextendr-conversions |
| Expose a Rust struct as an R6/S3/S4/S7/vctrs class | miniextendr-classes |
| data.frame ↔ Vec, DataFrameRow derive, builders | miniextendr-dataframe |
| Rayon, worker thread, SEXP-not-Send, thread control | miniextendr-parallel |
| Vendoring, CRAN submission, release CI | miniextendr-release |
Full manual (all framework docs, rendered): https://a2-ai.github.io/miniextendr