Skip to main content

Module condition

Module condition 

Source
Expand description

Condition macros and signal enum for the Rust→R condition pipeline.

This module provides two things:

  1. RCondition enum — the internal panic payload used by error!(), warning!(), message!(), and condition!() macros. Caught by crate::unwind_protect::with_r_unwind_protect before the generic panic→error path, then forwarded to R as a structured condition with rust_* class layering via crate::error_value::make_rust_condition_value.

  2. AsRError struct — wraps any E: std::error::Error and preserves the full error chain (cause/source) when converting to an R error message. Use as the Err type in Result returns.

§When to reach for what

There are three Rust→R error-emission paths and they are not interchangeable. The crate-level rationale (why tagged-SEXP at all, what error_in_r defaults imply, and the with_r_unwind_protect leak) lives on crate::error_value; the practical picking-one summary:

  • panic! — escape hatch. Becomes class c("rust_error", "simpleError", "error", "condition") with kind = "panic". Use for genuine bugs or impossible states. Cheapest in source; coarsest in R (callers can only match rust_error / error, not a specific class).
  • error! / warning! / message! / condition! (this module) — typed conditions. Same transport, but allow an optional class = "name" so R-side tryCatch can route by class. warning! / message! / condition! are the only way to emit non-error conditions; panic! is always an error.
  • Result<T, E> with AsRError<E> — value-style propagation through Rust code. Converts at the boundary; kind = "result_err". Best when the failure path is real-and-recoverable in Rust and the error chain (std::error::Error::source) is worth preserving.

Rf_error is not on this list. Direct Rf_error skips Rust destructors unconditionally and is forbidden by lint MXL300; see crate::error_value for the full reasoning.

§Macro-vs-module name collision

#[macro_export] puts each macro at the crate root, where error! and condition! collide with the same-named modules pub mod error and pub mod condition. The practical implication: use miniextendr_api::{error, condition} imports the modules, not the macros, and a subsequent error!(...) call fails to resolve.

Workarounds, in rough order of ergonomics:

  1. Invoke via fully-qualified path: miniextendr_api::error!("...").
  2. use miniextendr_api as mx; then mx::error!("...").
  3. warning! and message! have no module conflict — use miniextendr_api::{warning, message}; works directly.

See the individual macro docs for the per-macro reminder.

§Condition macros

The four macros are the user-facing API for raising non-panic conditions from Rust. They ride the tagged-condition transport that every #[miniextendr] function uses:

use miniextendr_api::{error, warning, message, condition};

#[miniextendr]
fn demo_error() {
    error!("something went wrong: {}", 42);
}

#[miniextendr]
fn demo_warning() {
    warning!("something looks suspicious");
}

#[miniextendr]
fn demo_message() {
    message!("progress: {} of {}", 1, 10);
}

#[miniextendr]
fn demo_condition() {
    condition!("a signallable condition");
}

Optional class = extension for programmatic catching:

#[miniextendr]
fn typed_error(name: &str) {
    error!(class = "my_error", "missing field: {name}");
}
tryCatch(typed_error("x"), my_error = function(e) "caught!")
# [1] "caught!"

Optional data = extension attaches structured fields readable as e$<name> in handlers (rlang-abort()-style):

#[miniextendr]
fn validate(value: i32) {
    if !(0..=100).contains(&value) {
        miniextendr_api::error!(
            class = "validation_error",
            data = [("value", value), ("min", 0), ("max", 100)],
            "value {value} out of range"
        );
    }
}
tryCatch(validate(150L), validation_error = function(e) c(e$value, e$min, e$max))
# [1] 150   0 100

Supported data value types (anything with RValue: From<_>): scalars and Vecs of i32, f64, bool, String / &str; their NA-aware Option / Vec<Option<_>> forms (None → R NA); the wide-integer ladder (i64 / u32, narrowed to integer(1) when it fits, double(1) otherwise); and the RValue::debug escape hatch, which stringifies any T: Debug. For nested lists or complex/raw/NA-bearing values build an RValue directly. The payload is built as a Send-safe owned value at the call site and materialised as R objects on the main thread — so data = works from worker-thread code too.

Three data = grammars are accepted (see crate::error!):

  • single pair: data = ("name", value)
  • bracketed list: data = [("a", v1), ("b", v2)]
  • keyed builder sugar: data = { value = 42, code = 7 } (bare-ident keys)

§AsRError

use miniextendr_api::condition::AsRError;

#[miniextendr]
fn parse_config(path: &str) -> Result<i32, AsRError<std::io::Error>> {
    let content = std::fs::read_to_string(path).map_err(AsRError)?;
    Ok(content.len() as i32)
}

Structs§

AsRError
Structured error wrapper that preserves the std::error::Error cause chain.

Enums§

RCondition 👻
Internal panic payload for structured R conditions.

Functions§

repanic_if_rust_error
Inspect a SEXP returned by a trait-ABI vtable shim and, if it is a tagged error value, re-panic with the reconstructed RCondition.

Type Aliases§

ConditionData
Named condition-data payload: an ordered list of (name, value) pairs.