This guide covers common error messages from #[miniextendr] proc macros and the miniextendr-lint static analysis tool.

🔗Running the Lint

just lint               # Run lint on rpkg

The lint also runs automatically during cargo build/cargo check via build.rs. To disable temporarily:

MINIEXTENDR_LINT=0 cargo check --manifest-path=rpkg/src/rust/Cargo.toml

🔗Lint Codes Reference

🔗Errors (CI-blocking)

CodeDescriptionFix
MXL001Reserved(Legacy lint, no longer applicable)
MXL002Reserved(Legacy lint, no longer applicable)
MXL003Reserved(Legacy lint, no longer applicable)
MXL004Reserved(Legacy lint, no longer applicable)
MXL005Reserved(Legacy lint, no longer applicable)
MXL006Reserved(Legacy lint, no longer applicable)
MXL007impl Type; requires ExternalPtr deriveAdd #[derive(ExternalPtr)] or implement TypedExternal
MXL008Trait impl class system incompatibleEnsure trait impl uses same class system as inherent impl
MXL009Multiple impl blocks without labelsAdd #[miniextendr(label = "unique")] to each impl block
MXL010Duplicate labels on impl blocksUse unique labels for each impl block

🔗Warnings (P0 — high impact)

CodeDescriptionFix
MXL100Duplicate entrypoint symbolRename one of the conflicting entries
MXL101Duplicate registration entriesRemove the duplicate entry
MXL102Trait impl missing TypedExternalImplement TypedExternal for the type
MXL103Generic concrete type in trait-ABIUse concrete (non-generic) types for trait ABI
MXL104#[cfg] mismatch between item and registrationEnsure #[cfg] attributes match on both
MXL105Unreachable module fileCheck file paths and module hierarchy
MXL106Registered function is not pubAdd pub to the function definition
MXL107Missing #[miniextendr] impl Trait for TypeAdd the attribute to the trait impl
MXL108Missing registration for trait implAdd #[miniextendr] to the trait impl
MXL109#[cfg] mismatch between mod declarationsEnsure #[cfg] attributes are consistent

🔗Warnings (P1 — important)

CodeDescriptionFix
MXL200Trait tag collision preflightUse unique trait names or labels
MXL201Impl label mismatchCheck label spelling matches across impls
MXL202Orphan child module referenceRemove the reference to non-existent child module
MXL203internal + noexport redundancyUse just #[miniextendr(internal)] (implies noexport)
MXL204Multiple root-level registrationsOnly one root registration per crate

🔗Warnings (P2 — safety)

CodeDescriptionFix
MXL300Direct Rf_error/Rf_errorcall callUse panic!() or return Err(...) instead
MXL301_unchecked FFI call outside guard contextUse the checked wrapper, or ensure you’re inside with_r_unwind_protect

🔗MXL300: Direct Rf_error calls

Rf_error() and Rf_errorcall() perform a C longjmp that skips Rust destructors. This leaks memory and can corrupt state. The lint detects these calls via text scanning.

Preferred alternatives:

  • panic!("message") — caught by miniextendr’s unwind protection, produces a structured R condition
  • return Err(...) — for Result<T, E> return types, produces a clean R error

When Rf_error is intentional (e.g., inside with_r_unwind_protect closures or test fixtures), suppress with // mxl::allow(MXL300) — see Inline Suppression below.

🔗MXL301: Unchecked FFI calls

Functions like Rf_ScalarInteger_unchecked() bypass miniextendr’s main-thread routing check. They are only safe when you are certain you’re on R’s main thread (inside ALTREP callbacks, with_r_unwind_protect closures, extern "C-unwind" functions called by R, etc.).

Preferred: Use the checked wrapper (without _unchecked suffix). It adds a debug-mode thread assertion.

When unchecked is intentional, suppress with // mxl::allow(MXL301).

🔗Inline Suppression

Both MXL300 and MXL301 support inline suppression via // mxl::allow(...) comments. The suppression comment can appear:

  1. On the same line (trailing comment):
Rf_error(c"%s".as_ptr(), c"intentional".as_ptr()) // mxl::allow(MXL300)
  1. On the immediately preceding line (standalone comment):
// mxl::allow(MXL300)
Rf_error(c"%s".as_ptr(), c"intentional".as_ptr())

Multiple codes can be suppressed in one comment:

// mxl::allow(MXL300, MXL301)
Rf_error_unchecked(c"test".as_ptr())

Important: The comment must be on the exact same line or the line directly above. A comment two lines above will not suppress the diagnostic.

// mxl::allow(MXL300)   <-- too far away (2 lines above)
unsafe {
    Rf_error(...)        <-- NOT suppressed
}

Move the comment inside the block:

unsafe {
    // mxl::allow(MXL300)
    Rf_error(...)        <-- suppressed
}

🔗Suppression syntax

// mxl::allow(CODE)
// mxl::allow(CODE1, CODE2)
// mxl::allow(CODE1, CODE2, CODE3)
  • Prefix: // mxl::allow(
  • Codes: comma-separated, whitespace around commas is ignored
  • Only MXL300 and MXL301 are currently suppressible

🔗Common Proc-Macro Errors

🔗“dots must be the last parameter”

The ... (dots) argument must appear last in the function signature:

// Wrong
#[miniextendr]
fn bad(dots: &Dots, x: i32) -> i32 { x }

// Correct
#[miniextendr]
fn good(x: i32, dots: &Dots) -> i32 { x }

🔗“expected pub function”

Only pub functions get @export in R wrappers. If the function is intentionally private, use #[miniextendr(noexport)] or #[miniextendr(internal)].

🔗“multiple impl blocks for Type need labels”

When a type has more than one #[miniextendr] impl block, each needs a unique label:

#[miniextendr(label = "core")]
impl MyType {
    fn method_a(&self) -> i32 { 0 }
}

#[miniextendr(label = "extra")]
impl MyType {
    fn method_b(&self) -> String { String::new() }
}

🔗“trait impl class system incompatible”

A trait impl’s class system must match the inherent impl’s class system:

#[miniextendr(s3)]
impl MyType { /* ... */ }

// This will error if the trait impl uses a different class system
#[miniextendr]  // Must inherit s3 from the inherent impl
impl Display for MyType { /* ... */ }

🔗“type must derive ExternalPtr or implement TypedExternal”

Types used in #[miniextendr] impl blocks need pointer identity:

#[derive(ExternalPtr)]
struct MyType { /* ... */ }

#[miniextendr]
impl MyType { /* ... */ }
// Registration is automatic via #[miniextendr].

🔗Debugging Tips

  1. Run just lint before building — it catches attribute issues earlier than compile errors
  2. Check NAMESPACE — if a function exists in Rust but not in R, run just devtools-document
  3. Feature-gated modules — use #[cfg] on mod declarations for conditional compilation