Guide
Full Project Tutorial
Walk through the pigworld model project from package skeleton to Rust runtime, configure glue, and generated R wrappers.
This guide uses tests/model_project/ as a concrete end-to-end example. It is a much better tutorial reference than a minimal hello-world because it shows a full package with configure logic, a nested Rust crate, generated R wrappers, and a nontrivial Rust type exported to R.
🔗Why this project is useful
The model project is a scaffolded package called pigworld. It includes:
- package metadata in
DESCRIPTION,NAMESPACE, andR/ - a nested Rust crate in
src/rust/ - configure/bootstrap/build glue for the R package build
- generated R wrappers and roxygen output
- a real exported Rust-backed
Worldtype instead of a single toy function
In other words: it shows the whole shape of a package, not just one boundary crossing.
🔗Step 1: the package shell
Start with the R package files:
tests/model_project/DESCRIPTIONtests/model_project/NAMESPACEtests/model_project/R/pigworld-package.R
These establish the installed package name (pigworld), register the dynamic library, and provide the minimal R-side package shell that the generated wrappers plug into.
This is the layer that should still look familiar to an R package author. miniextendr does not replace the package shell; it fills in the Rust side and the wrapper generation workflow.
If the extra DESCRIPTION entries look unfamiliar, read DESCRIPTION Fields for miniextendr Packages. That page explains what the Config/build/*, SystemRequirements, roxygen, and testthat lines are doing there.
🔗Step 2: the Rust crate inside the package
The Rust code lives in:
tests/model_project/src/rust/Cargo.tomltests/model_project/src/rust/lib.rs
Cargo.toml shows the standard shape of a nested package-local Rust crate:
crate-type = ["staticlib"]for linking into the R package- a package-local standalone
[workspace] - dependency on vendored
miniextendr-api - optional features forwarded into the runtime crate
lib.rs shows a more realistic exported type than a trivial function. The project defines a World struct, derives ExternalPtr, and exports methods through #[miniextendr]:
#[derive(miniextendr_api::ExternalPtr)]
pub struct World {
pigs: Vec<Pig>,
tick: u32,
next_id: u64,
rng: rand::rngs::StdRng,
food_per_tick: f64,
move_cost: f64,
reproduction_threshold: f64,
max_age: u32,
interaction_radius: f64,
}
#[miniextendr]
impl World {
pub fn new(...) -> Self { ... }
pub fn step(&mut self) { ... }
pub fn run(&mut self, steps: i32) { ... }
pub fn summary(&self) -> String { ... }
}
That is the key teaching point: the Rust side still looks like an ordinary type with methods. The R-facing API is derived from it, rather than hand-written separately.
🔗Step 3: the build and configure glue
The files that make this a real R package build are:
tests/model_project/bootstrap.Rtests/model_project/configure.actests/model_project/configuretests/model_project/src/Makevars.intests/model_project/src/rust/cargo-config.toml.intests/model_project/src/stub.c
This layer answers the questions that a toy example usually skips:
- how
R CMD INSTALLfinds Cargo and Rust - how dev and CRAN-like modes are selected
- where cargo outputs go
- how package-local Rust config is generated
- how the static library gets linked into the final package library
bootstrap.R is especially useful to study because it shows how devtools workflows force a predictable configure mode before the package build starts.
🔗Step 4: wrapper generation and R-facing methods
The generated R API lives in:
tests/model_project/R/pigworld-wrappers.Rtests/model_project/man/World.Rd
This is what the user eventually interacts with from R:
world <- World$new(
n_initial = 50L,
food_per_tick = 4,
move_cost = 1,
reproduction_threshold = 80,
max_age = 100L,
interaction_radius = 2,
seed = 42L
)
world$run(100L)
world$summary()
The important thing to notice is that the wrappers are generated from the Rust impl block and Rust doc comments. The R methods, Rd entries, and method dispatch shape all come out of the Rust source.
🔗Step 5: how to read this project as a tutorial
If you want to follow it as a step-by-step walkthrough, read it in this order:
DESCRIPTIONandNAMESPACEto understand the package shell.src/rust/Cargo.tomlto understand how the nested Rust crate is configured.src/rust/lib.rsto see the actual exported Rust API.bootstrap.Randconfigure.acto understand how package builds are wired.R/pigworld-wrappers.Rto see what miniextendr generates for R users.man/World.Rdto see the final user-facing documentation shape.
That sequence gives you a full-project mental model without having to infer the missing pieces from smaller examples.
🔗What this tutorial teaches better than a minimal example
Compared with a simple add(a, b) tutorial, the model project shows:
- an exported Rust-backed class instead of just free functions
- package bootstrap and configure behavior
- Cargo feature wiring in a package-local crate
- generated R method wrappers
- generated Rd output
- the actual shape of a scaffolded package you can compare against your own
🔗Where to go next
- Read Getting Started for the minimal first package flow.
- Read Packages if you want the repository-wide map of crates and R packages.
- Read Package Map for the detailed relationship between workspace crates, R packages, and fixtures.