extendr generates the FFI layer so your R package can call Rust
R extension library for rust designed to be familiar to R users.
At a glance
- What is it?
- extendr is a suite of Rust crates that lets R code call Rust functions through wrappers generated by a procedural macro, with a separate R package called rextendr handling the R side of the workflow. The pitch is type safety over hand written C interface code, and the cost is a toolchain that needs Rust, R and a task runner before anything runs.
- Who is it for?
- extendr fits an R package team that wants a performance sensitive core in Rust while keeping the package structure and the R API familiar, and it does not fit a team that wants zero build tooling or a fully automatic binding for an existing Rust crate, because the goal of wrapping Rust libraries without markup is still stated as a future aim.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
This repository holds the Rust crates, not the R package
The first thing to sort out about extendr is that the name refers to a suite of packages, not one thing in one repository. The README states that this repository is for the Rust crates that are part of extendR, and points to the separate R package `{rextendr}` as the piece that facilitates using extendR from an R session. A complete user guide lives on the extendR website, and the main crate, `extendr-api`, is published on crates.io with API documentation on docs.rs.
The Rust side is a cargo workspace with four members: `extendr-api`, `extendr-engine`, `extendr-ffi` and `extendr-macros`. The split is the architecture. The FFI layer is isolated in `extendr-ffi`, the code generation lives in `extendr-macros`, the user facing API is `extendr-api`, and `extendr-engine` completes the four-crate workspace. A `tests/` directory holds an R package, `{extendrtests}`, that carries its own Rust crate under `tests/extendrtests/src/rust`, so the workspace exercises the boundary from both sides.
The positioning is stated plainly in the README: the interface is intended to be easier to use than the C interface and Rcpp, because Rust gives type safety and freedom from segfaults. For an audience deciding whether to adopt it, that is the comparison that matters, and the rest of the design follows from it.
The attribute macro writes the wrapper and the registration glue
The mechanism is a procedural macro. You mark a struct, an impl block or a function with `#[extendr]`, and the compiler generates the wrapper and registration functions for R, which are called when the package is loaded. A second macro, `extendr_module!`, lists the module name and the exported functions and interfaces, which is the manifest R consults when the native library loads.
A short example from the README shows a struct with one field and an impl block with three methods:
#[extendr]
struct Person {
pub name: String,
}
#[extendr]
impl Person {
fn new() -> Self {
Self { name: "".to_string() }
}The point of the `impl` block is that methods become R methods on the generated class, so the object model on the R side mirrors the Rust one rather than flattening into a list of function calls. The `extendr_module!` block at the bottom of the file then names what to export, for example `impl Person;` and `fn aux_func;`, so adding a Rust function still requires editing the manifest, which is one of the places where the promise of a light touch stops.
The plain function case is the smallest possible example, and it is the one to copy when you start:
#[extendr]
pub fn my_sum(v: &[f64]) -> f64 {
v.iter().sum()
}A slice of doubles in, a double out, with no R object in sight. That is the design decision the rest of the crate is built around.
Arguments become Rust types on entry, not wrapped R objects
The stated goal of the project is the opposite of what most binding layers do. Instead of wrapping R objects, extendr converts them to Rust native objects on entry to a function, and the README says this makes the wrapped code clean and dependency free. The longer term goal is to allow wrapping existing Rust libraries without any markup at all, but the README is honest that this has not arrived yet and that in the meantime the markup is as light as possible.
That trade has a consequence for you. If your Rust function takes `&[f64]`, R vectors of doubles are converted for you and you never touch the R API. If it needs to work with an arbitrary R object, you are out of the convenient path, and that is what the `Robj` type is for. `Robj` wraps the native R object type and supports a large subset of the R internals functions, but wrapped so that the operations are prevented from causing accidental segfaults and failures.
The practical reading of this design is that extendr is at its best with numeric and string data crossing the boundary, where conversion is cheap and the type system does the work, and at its worst when you need to keep passing live R objects back and forth. The escape hatch exists, and it is a deliberate one, but it moves you back to a lower level of care.
rextendr is where you actually work from an R session
Since this repository is the Rust side, the first question for any new user is how to get from R to a running Rust function. The README lists three routes, and the distinction between them matters more than it first appears.
For quick work in an interactive R session, the `rextendr::rust_function` family of helpers is there to prototype Rust code without setting up a package. In an R package context, `rextendr::use_extendr()` sets up a Rust powered R package, and there is a vignette on building R packages with it. Third, it is possible to inline Rust code in RMarkdown and knitr documents through the `knitr-engine` vignette, which is a good way to test whether Rust is even the right tool for a given task before wiring it into a package.
None of these steps are described in detail here, and that is a real gap for a reader making a decision. The README does not show what `rust_function` returns, how the compile and load step behaves, or how errors from Rust surface in R. The documentation site and the vignettes are the places to look for that, and they sit outside this repository.
What is clear from the repository itself is that the R package and the Rust crates are versioned and released separately. Any adoption plan has to track both, and the mismatch between the two release streams is where version surprises will come from.
A four crate workspace pinned to Rust 1.77
The workspace definition in the root `Cargo.toml` is short and tells you the compatibility story:
[workspace]
resolver = "2"
members = [
"extendr-api",
"extendr-engine",
"extendr-ffi",
"extendr-macros",
]The shared package metadata sets the version to 0.9.0, the edition to 2021 and the minimum supported Rust version to 1.77, and the licence to MIT. The two internal dependencies, `extendr-ffi` and `extendr-macros`, are declared with both a path and a version, and a comment in the file notes that updating extendr's version means updating that version field too. That comment is a small but telling detail about maintenance: this is a workspace where a version bump touches several places by hand.
The dependency graph for a user is narrow. `extendr-api` is the crate you depend on, and the heavier `extendr-ffi` and `extendr-macros` parts come along as part of the generated wrapper machinery rather than as something you configure. The MSRV of 1.77 is the number to check against your build image, because it is the oldest toolchain that can still compile the crate, and on a locked down CI runner that pin decides whether your build works at all.
The justfile is the actual developer interface
Contributing to extendr means installing `just`, either from its repository or with `cargo install just`, and then reading the recipes with `just --list`. The README calls out the key ones, and the `justfile` in the repository root shows what they actually run. Formatting is checked and applied with `just fmt-check` and `just fmt`, and it covers the workspace plus the Rust crate inside the R test package. Linting is clippy with warnings treated as errors, both for the workspace and for the test crate:
fmt-check *cargo_flags:
cargo fmt --all {{cargo_flags}} -- --check
cargo fmt --all --manifest-path=tests/extendrtests/src/rust/Cargo.toml {{cargo_flags}} -- --checkThe rest of the recipes cover the parts that only make sense for this project. `just r-cmd-check` runs R CMD check for `{extendrtests}`, and it takes an argument that decides whether warnings are errors. `just doc` and `just doc-check` build the documentation, and the corresponding cargo invocations in the `justfile` use a nightly toolchain, the `full-functionality` feature and the `--document-private-items` flag. `just msrv` verifies the minimum supported Rust version, `just devtools-test` runs the R package tests through devtools and can accept snapshots with `SNAPSHOT=1`, and `just document` regenerates the wrappers for `{extendrtests}` through `rextendr::document()`.
Two things stand out. Documentation builds need nightly Rust, which is a heavier requirement than the MSRV implies, and the wrapper regeneration step is a separate manual action, so a change in the macros shows up as a diff in generated R code that someone has to commit.
Two release streams, one MIT licence
extendr is MIT licensed, and the workspace metadata repeats that, so using the crates inside a closed source R package is a straightforward licensing question rather than a copyleft one. The academic side is also documented, with a JOSS DOI badge and a `CITATION.cff` file in the repository root, along with a `CHANGELOG.md` and a `CONTRIBUTORS.md`.
The versioning is where the confusion sits. The recent releases carry two different naming schemes: `extendr-api-v0.8.2`, tagged with the `extendr v0.8.2` release, and a separate `v0.9.0` tag, with `extendr-api-v0.8.0` before them. The workspace itself is at version 0.9.0, matching the `v0.9.0` release from 2026-04-16, while the most recent `extendr-api` tag on the list is 0.8.2 from 2026-07-31. So the API crate and the workspace are not moving on the same schedule, and a dependency on `extendr-api` from crates.io and a dependency on the workspace git tag are two different things to reason about.
The last push to the repository was on 2026-10-15 and the project is not archived, so work is landing. For anyone integrating extendr into an R package, the practical step is to pin the exact `extendr-api` version you build and test against, and to regenerate the wrappers with `just document` after any macro change, rather than tracking the main branch and hoping.
Editorial conclusion
extendr fits an R package team that wants a performance sensitive core in Rust while keeping the package structure and the R API familiar, and it does not fit a team that wants zero build tooling or a fully automatic binding for an existing Rust crate, because the goal of wrapping Rust libraries without markup is still stated as a future aim. Before you start, check `Cargo.toml` in the workspace root for the current MSRV, listed as 1.77, and read the `justfile` recipes, since the contributor workflow assumes `just` and a nightly Rust toolchain for documentation builds.
Frequently asked questions
What is extendr and how does it connect R and Rust?
extendr is a suite of Rust crates that expose Rust functions to R. The `#[extendr]` attribute macro generates the wrapper and registration functions for R, and the `extendr_module!` macro lists which modules, functions and interfaces are exported.
How do I call Rust from an R session with extendr?
The R side is the separate `{rextendr}` package. In an interactive session `rextendr::rust_function` and its helpers are used to prototype Rust code, `rextendr::use_extendr()` sets up a Rust powered R package, and the knitr-engine vignette covers inlining Rust in RMarkdown documents.
How does extendr differ from Rcpp and the plain C interface?
The README states that extendr aims to be easier to use than the C interface and Rcpp, with Rust providing type safety and freedom from segfaults. Its design converts R objects to Rust native types on entry rather than wrapping R objects, and the `Robj` type exists for cases where you must handle live R objects safely.
What is the minimum Rust version extendr requires?
The workspace `Cargo.toml` sets the rust-version to 1.77 and the edition to 2021. The contributor tooling includes a `just msrv` recipe that verifies the minimum supported Rust version, and documentation builds use a nightly toolchain.
Can I wrap an existing Rust library with extendr without any markup?
Not yet, according to the README, which describes allowing existing Rust libraries to be wrapped without markup as the ultimate goal and says that for now the markup is as light as possible. You still mark items with `#[extendr]` and list them in the `extendr_module!` block.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/extendr-extendr)Was this useful?