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§

ArgError
An argument error as a value: the parameter’s R name and the message. ArgError::raise raises it as the generated wrapper’s own argument error, through crate::arg_error!.
AsRError
Structured error wrapper that preserves the std::error::Error cause chain.
ErrParts 👻
The parts of a condition the generated Err arms hand to crate::error_value::result_err_condition_value / crate::error_value::conversion_condition_value, and that RCondition::into_parts splits a payload into: message, user classes, data fields and the ConditionCall choice.
RError
A ready-made classed error value for Result<T, RError> returns.
SerdeErrorShape
The options of #[miniextendr(serde_error(..))] as a value: declared once, shared by every function that returns one error type.
ShapeMessage 🔒
A const-evaluation panic message. const fn can’t format!, so the parts are copied into a fixed buffer.

Enums§

ConditionCall
Whether a condition reports a call (conditionCall()): the per-condition equivalent of R’s warning(..., call. = FALSE) / stop(..., call. = FALSE).
RCondition
Owned payload for structured R conditions.

Constants§

CONVERSION_ERROR_CLASS_MARKER 👻
The class that stands for the crate’s conversion_error_class in a tagged condition value (#1740).
CONVERSION_PARAM_FIELD 🔒
The name of the structured field that carries the failing parameter’s R name on every argument-conversion condition (e$param).
CONVERSION_RUST_TYPE_FIELD 🔒
The name of the structured field that carries the Rust type an argument failed to convert to (e$rust_type, #1591).
PARENT_FIELD 🔒
The data field under which RCondition::with_parent sends a cause to R; the raise helper turns it into the condition object e$parent.
RESERVED_CONDITION_FIELDS
Condition-data field names that would overwrite the condition object’s own slots when the R helper splices data in (utils::modifyList over list(message, call, kind)).
SHAPE_TYPE_OPTIONS 🔒
The as_* options, in the order of SerdeErrorShape’s type lists.

Traits§

ConditionClass
What the condition macros’ class = … (and RError::class) accept: one class or several, as &str / String / arrays / Vec / slices.
ConversionArgMessageBuiltin 👻
Whole-message probe, built-in arm: a match_arg choice error, as a MatchArgError or at the top of a SexpError (#[derive(MatchArg)]’s TryFromSexp returns one), words the whole argument error the way the wrapper’s R-side match_arg check (.miniextendr_match_arg) does, from the same MatchArgError::arg_error (#1767): 'mode' should be one of "fast", "slow", 'mode' must be of length 1, 'mode' must be NULL or a character vector. So a match_arg type converted without #[miniextendr(match_arg)] and one checked by the wrapper raise the same message.
ConversionArgMessageClassed 👻
Whole-message probe, classed arm: any RConditionData answers with its argument_message, so a type’s own conversion error can word the whole argument error (#1833). Same autoref level as ConversionArgMessageBuiltin, like ConversionErrClassed: no built-in error type implements RConditionData.
ConversionArgMessageNone 👻
Whole-message probe, fallback arm: any other error leaves the message to the prefix and the error’s reason.
ConversionErrBuiltin 👻
Conversion probe, built-in arm: the framework’s own conversion errors (SexpError and the SexpTypeError / SexpClassError / SexpLengthError / SexpNaError it wraps, and MatchArgError), reworded for R users (#1591).
ConversionErrClassed 👻
Conversion probe, preferred arm: E: RConditionData.
ConversionErrDisplay 👻
Conversion probe, fallback arm: any E: Display. The message is the error’s Display text, with no class and no data, so the condition keeps the plain rust_error class vector.
ConversionExpectBuiltin 👻
Expectation probe, built-in arm: an error that knows what the value should have been. A match_arg choice error (MatchArgError, or a SexpError carrying one, as #[derive(MatchArg)]’s TryFromSexp returns) names its choices: one of "fast", "slow"; a class error names the class (a data frame); an Either whose arms both refused the kind of value names both (one of "fast", "slow", or numeric).
ConversionExpectNone 👻
Expectation probe, fallback arm: any other error type says nothing about what the value should have been.
ErrPartsClassed 👻
Autoref-specialisation probe, first stage, preferred arm: E: RConditionData.
ErrPartsDebug 👻
Second stage, fallback arm: any E: Debug (the historical format!("{e:?}") rendering, no class, no data).
ErrPartsSerde 👻
Second stage, preferred arm: E: Serialize + Display under the serde feature, classed via [serde_err_parts] with the defaults (kind tag, the <crate>_error prefix the macro passes in, no shapes). #[miniextendr(serde_error(..))] bypasses the probe with explicit options.
ErrPartsUnclassed 👻
First stage, fallback arm: no RConditionData impl.
RConditionData
Give a Result<T, E> error type an R class vector and structured fields.

Functions§

arg_check_parts 👻
The parts of an argument check that failed in Rust after the conversion: #[miniextendr(no_na)] on a reading marker (AsNumeric*, AsCharacter*) whose converted value holds an NA that R’s anyNA() did not see (the hidden TryFromSexp::__mx_has_na says so), or on an Either, which has no R guard, an NA the arm taken refuses (TryFromSexp::__mx_input_has_na).
builtin_reason_parts 🔒
The R-facing reason for a built-in conversion error, see ConversionErrBuiltin.
catch_condition
Run f, returning a condition raised inside it as Err instead of letting it reach R; any other unwind carries on.
check_condition_data
Apply check_condition_field_name to every field of a payload.
check_condition_field_name
Runtime half of the reserved-name check, for computed field names (the macros’ expr path and RConditionData::data on user types). Panics when name is reserved; the panic becomes a plain rust_error in R, replacing the former silent overwrite.
check_field_list 🔒
A field list: set once, not empty, each name non-empty and listed once.
conflict 🔒
const_contains 🔒
conversion_err_parts 👻
Combine an argument-conversion error’s own parts with the wrapper’s parameter context.
conversion_prefix 👻
The message prefix of a conversion failure whose expectation comes from the error at run time (see crate::__mx_conversion_expectation!): '<p>' must be <expected> ('<p>' must be NULL or <expected> for an Option<_> parameter, nullable), else invalid '<p>' argument.
crate_conversion_error_class 🔒
The package crate’s conversion_error_class, from the entry miniextendr_init! registers in MX_CONVERSION_ERROR_CLASS (each package links its own copy of this crate, so it is that package’s), or on wasm32 from the wasm_registry.rs snapshot (#1768). Empty when the crate sets none.
fail 🔒
Panic with message. #[track_caller] here and on every caller puts the compile error at the user’s const, not in this crate.
is_no_call_marker 🔒
true for the tagged value’s “no call” marker in the call slot: a length-1 logical FALSE (ConditionCall::None). A call object or NULL is not the marker.
is_reserved_condition_field
true for RESERVED_CONDITION_FIELDS. const so the macros can reject a literal field name at compile time:
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.
resolve_conversion_error_class_marker 🔒
class with CONVERSION_ERROR_CLASS_MARKER replaced by crate_classes, in place, as the generated .miniextendr_raise_condition replaces it in R. A raising guard has no generated R to do it, so it calls this with crate_conversion_error_class.
scalar_option 🔒
tag / prefix: set once, never empty.
set_twice 🔒
str_eq 🔒
a == b, usable in const fn.

Type Aliases§

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

Derive Macros§

RCondition
#[derive(RCondition)]: RConditionData generated from a type’s shape (#[condition(class = "…")], #[condition(message = "…")], the rename / skip / debug field options) and From<T> for RCondition with the declared kind (#[condition(kind = …)], default error). Derive an R condition type for a struct or enum: RConditionData (the R class vector, the message and the data fields handlers read as e$<name>) and From<Self> for miniextendr_api::RCondition.