Expand description
Deferred conditions: signal an R warning, message or condition and still return a value.
crate::warning!, crate::message! and crate::condition! unwind
the Rust function: R sees the signal, the call returns invisible(NULL).
That is the right shape for “stop here, but softly”, not for “here is the
result, and by the way …”. The defer_* family queues the condition
instead; the generated C wrapper signals everything queued during the call
once the Rust body has returned, in queue order, and then hands the value
to R.
use miniextendr_api::{defer_warning, miniextendr};
use miniextendr_api::RCondition;
#[derive(Debug, RCondition)]
#[condition(class = "pkg_warning")]
enum PkgWarning {
#[condition(message = "dropped {dropped} of {total} rows")]
Truncated { dropped: i32, total: i32 },
}
#[miniextendr]
fn trim(n: i32) -> i32 {
defer_warning(PkgWarning::Truncated { dropped: 2, total: n });
n - 2
}trim(5L)
# [1] 3
# Warning message:
# In trim(5L) : dropped 2 of 5 rows
w <- tryCatch(trim(5L), pkg_warning_truncated = function(w) w)
w$dropped # 2
suppressWarnings(trim(5L)) # 3§What R sees
Exactly what warning() / message() / signalCondition() called from
an R function would produce, because that is what runs: the same R helper
the generated wrappers use for the panicking macros re-raises each queued
condition. Class layering (c(<classes…>, "rust_warning", "simpleWarning", "warning", "condition")), data fields as w$<name> and
conditionCall() (the wrapper’s call, or NULL for a condition queued
with call = none) are identical to the immediate forms. tryCatch(warning = ) exits the call before the value is returned;
withCallingHandlers + invokeRestart("muffleWarning") keeps it;
suppressWarnings() / suppressMessages() work as usual.
§Ordering
Queued conditions are signalled after the Rust body finished and before R
sees its outcome, so a deferred warning followed by rust_error! (or an
Err return) is signalled first and the error second, and two deferred
conditions keep their queue order. Nested calls (R code evaluated from
Rust that calls back into another #[miniextendr] function) flush only
what they queued themselves.
§Capturing a block’s conditions
capture runs a closure and returns what it queued instead of
signalling it, for code whose diagnostics the caller already has (a
replay of the steps that built a saved object). Conditions queued outside
the closure are untouched.
§Warning placement
Conditions queued before a failure still signal. If a body must not warn on
failure, collect warning payloads locally and queue them only after its last
fallible step (including any ?). Moving a warning before a later ? changes
that behavior: returning Err does not cancel the queued warning.
use miniextendr_api::{defer_warning, miniextendr};
#[miniextendr]
pub fn parse_trimmed(text: String) -> Result<i32, std::num::ParseIntError> {
let trimmed = text.trim();
let value = trimmed.parse::<i32>()?;
if trimmed != text {
defer_warning!(class = "pkg_trimmed", "removed surrounding whitespace");
}
Ok(value)
}Here, parsing " bad " returns an error without a warning; parsing " 42 "
queues the whitespace warning and returns 42.
§Payloads
defer_warning(), defer_message() and defer_condition() take any
RConditionData value: a #[derive(RCondition)] enum or struct
(class from the type and variant names, message from a format! string or
Display, fields as data), or a hand-built crate::condition::RError.
The defer_warning! /
defer_message! /
defer_condition! macros take the
crate::warning! grammar (class = …, data = …, format message) for
one-off conditions.
§Threads and boundaries
The queue is a process-wide Mutex, so #[miniextendr(worker)] bodies
push from the worker thread; the signalling always happens on R’s main
thread after the worker result is back. Every generated .Call entry
point and every trait-ABI vtable shim flushes what was queued inside it.
ALTREP RUnwind / RustUnwind callbacks and connection I/O callbacks also
signal locally, with conditionCall() = NULL; raising guards use their
explicit call argument. An R-origin error discards the abandoned queue.
External-pointer finalizers and connection close/destroy/Drop callbacks
suppress deferred conditions: R finalization removes the caller’s handlers.
No R signalling or delayed delivery is attempted from destructors guarded
by drop_catching_panic. Unguarded raw FFI / AltrepGuard::Unsafe callbacks
must not queue conditions; choose a guarded callback instead.
Structs§
- Suppress 🔒
- Finalization has no caller condition handlers. Suppress queues throughout the destructor, including any nested framework boundaries, without R calls.
Constants§
Statics§
- PENDING 🔒
- Conditions queued during the current guarded callback (and enclosing calls).
Functions§
- capture
- Run
fand return the conditions it queued, oldest first, instead of signalling them when the guarded call returns. - defer 👻
- Queue an already-built condition payload. Used by the
defer_*!macros. - defer_
condition - Queue a plain condition (
signalCondition(): silent without a handler) to be signalled when the current guarded call or callback returns. - defer_
message - Queue a message (
message(), muffled bysuppressMessages()) to be emitted when the current guarded call or callback returns. - defer_
warning - Queue a warning to be signalled when the current guarded call or callback returns; the call still returns its value.
- discard 🔒
- Discard conditions whose Rust/R call was abandoned by an R longjmp.
- finish 👻 ⚠
- Signal everything queued since
mark, then returnvalue. - finish_
guarded 🔒 - Flush a non-raising callback’s conditions after its Rust guard has finished.
R objects in
valuemust already be rooted (the same contract asor_raise). - mark 👻
- The queue position at the start of a call. Pair with
finish. - pending 🔒
- raise_
condition_ 🔒helper - The
.miniextendr_raise_conditionclosure, evaluated once from the shared source (crate::registry::RAISE_CONDITION_HELPER_FN) in the base namespace and preserved for the life of the process. - signal_
now 🔒 ⚠ - Materialise each queued condition and hand it to the R helper.
- take_
pending 👻 - Remove and return everything queued since
mark, oldest first. Entries belowmarkbelong to enclosing calls and stay.