Reference page
Automatic Registration & Package Initialization
This document explains how miniextendr automatically registers R-callable functions and initializes the runtime when an R package loads.
This document explains how miniextendr automatically registers R-callable functions and initializes the runtime when an R package loads.
πOverview
When R loads a packageβs shared library, it calls R_init_<pkgname>(). In
miniextendr, this function is generated entirely in Rust via the
miniextendr_init! proc macro β no C entry point file is needed.
πHow It Works
π1. Annotate with #[miniextendr]
Every function, impl block, or trait annotated with #[miniextendr]
self-registers at link time via linkme distributed
slices. No manual module declarations are needed.
use miniextendr_api::miniextendr;
#[miniextendr]
pub fn hello() -> &'static str {
"Hello from Rust!"
}π2. Add miniextendr_init! to lib.rs
The miniextendr_init! macro generates the R_init_<pkgname>() function:
// lib.rs
use miniextendr_api::miniextendr;
#[miniextendr]
pub fn hello() -> &'static str {
"Hello from Rust!"
}
miniextendr_api::miniextendr_init!(mypkg);π3. Package loads β everything is automatic
When R runs library(mypkg), it calls R_init_mypkg(), which the macro
expanded into Rust code that does all initialization.
πWhat miniextendr_init! Does
The macro generates a single #[no_mangle] unsafe extern "C-unwind" function
that calls package_init(). This consolidates all initialization into one step:
// Expanded (simplified) β you never write this manually
#[no_mangle]
unsafe extern "C-unwind" fn R_init_mypkg(dll: *mut DllInfo) {
package_init(dll, b"mypkg\0");
}
package_init() performs the following in order:
- Install panic hook β captures panic messages for R error reporting
- Initialize runtime β records main thread ID; with
worker-threadfeature, also spawns the worker thread - Assert UTF-8 locale β fails fast if locale isnβt UTF-8
- Register ALTREP classes β registers all
#[derive(Altrep*)]classes - Register trait ABI β calls
mx_abi_register()for cross-package trait dispatch (mx_wrap,mx_get,mx_query) - Register routines β calls
R_registerRoutines()with all linkme- collected.Callentries - Lock down symbols β
R_useDynamicSymbols(dll, FALSE)andR_forceSymbols(dll, TRUE)
πThe stub.c File
Rβs build system requires at least one .c file in src/ to invoke the
linker. Since all entry points are now defined in Rust, we include a minimal
stub.c:
// Minimal stub so R's build system produces a shared library.
// All entry points (R_init_*) are defined in Rust via miniextendr_init!().
The extern reference to miniextendr_force_link forces the linker to pull
in the Rust archive member containing R_init_<pkg> and all linkme entries.
Without it, the linker would extract nothing from the staticlib.
πLinkme Distributed Slices
The #[miniextendr] proc macro generates a #[distributed_slice] entry for
each annotated item. At link time, all entries are collected into a static
slice. During package_init(), this slice is iterated to build the
R_CallMethodDef array for R_registerRoutines().
#[miniextendr] fn foo() β linkme slice entry for "C_foo"
#[miniextendr] fn bar() β linkme slice entry for "C_bar"
β
package_init() iterates slice
β
R_registerRoutines(dll, NULL, call_methods, NULL, NULL)πLinker Anchor (codegen-units = 1)
Static libraries (.a) strip unreferenced archive members during linking.
With codegen-units = 1 in Cargo.toml, the entire user crate compiles
into a single .o file inside the staticlib archive. stub.c references
miniextendr_force_link (emitted by miniextendr_init!), which forces the
linker to pull in that single archive member β bringing all linkme
distributed_slice entries along. No platform-specific force-load flags needed.
πR Wrapper Generation
R wrapper functions (the .R file with .Call() invocations) are generated
via a cdylib-based approach:
cargo rustc --crate-type cdylibbuilds a temporary shared library- R loads it via
dyn.load()and callsminiextendr_write_wrappers(path) - The function reads the linkme slices and writes R wrapper code to a file
roxygen2::roxygenise()generates NAMESPACE exports from the wrappers
This runs during just devtools-document or equivalent.
πWhen APIs Can Be Called
| API Category | When Safe |
|---|---|
| Panic hook | Anytime after package_init() |
| R APIs (value-returning) | After init, on main thread (or via with_r_thread with worker-thread feature) |
| R APIs (pointer-returning) | Main thread only, after init |
| Trait ABI | After init |
| User Rust functions | After init |
πMinimal Example
For a package named myrust:
// src/rust/lib.rs
use miniextendr_api::miniextendr;
#[miniextendr]
pub fn add(x: i32, y: i32) -> i32 {
x + y
}
miniextendr_api::miniextendr_init!(myrust);
Thatβs it β no C files to write, no module declarations, no manual registration.
πEmbedding R (miniextendr-engine)
When embedding R in a Rust application (not an R package), initialization differs slightly:
use miniextendr_engine::REngine;
fn main() {
// REngine::build() handles R initialization
let _r = REngine::build().unwrap();
// After this, you can call R APIs
// miniextendr_runtime_init() is called automatically
}
Additional functions available when embedding:
miniextendr_encoding_init()- Initialize UTF-8 locale handling (non-API)
These arenβt available in R packages because they reference symbols not exported from libR.
πTroubleshooting
πβminiextendr_runtime_init() must be calledβ
This panic means R API functions were called before initialization. Ensure
miniextendr_init! is present in your lib.rs.
πThread check failures
If is_r_main_thread() returns incorrect results, something called
miniextendr_runtime_init() from the wrong thread. The init must run on Rβs
main thread (which miniextendr_init! guarantees since R_init_* is called
by R on its main thread).
πSymbol not found errors
Ensure miniextendr_init!(pkgname) matches the package name in DESCRIPTION
and Cargo.toml. The crate name must use underscores (not hyphens) for the
R_init_* C symbol.
πSee Also
- ARCHITECTURE.md β High-level crate and call flow overview
- MINIEXTENDR_ATTRIBUTE.md β Complete
#[miniextendr]reference - R_BUILD_SYSTEM.md β How R builds packages with compiled code
- LINKING.md β Shared library linking strategy (libR discovery)