Expand description
Support for R’s missing arguments.
When an R function is called without providing a value for a formal argument,
R passes SEXP::missing_arg() as a placeholder. This is different from NULL -
a missing argument means “not provided”, while NULL is an explicit value.
§Example
use miniextendr_api::{miniextendr, Missing};
#[miniextendr]
fn greet(name: Missing<String>) -> String {
match name.into_option() {
Some(n) => format!("Hello, {}!", n),
None => "Hello, stranger!".to_string(),
}
}In R:
greet("Alice") # "Hello, Alice!"
greet() # "Hello, stranger!"§Difference from Option<T>
Option<T>treatsNULLasNoneand any other value asSome(T).Missing<T>treatsSEXP::missing_arg()as missing and any other value (includingNULL) as present.
Use Missing<Option<T>> if you need to distinguish between:
- Missing argument (not passed)
NULL(explicitly passedNULL)- A value (explicitly passed a non-NULL value)
§How it works: the quote(expr=) sentinel
In regular R functions, you detect missing arguments with missing():
my_func <- function(x) {
if (missing(x)) "not provided" else x
}
my_func() # "not provided"
my_func(5) # 5However, .Call() (the FFI boundary) forces argument evaluation, so an
omitted argument errors before the call even reaches Rust:
wrapper <- function(x) .Call(rust_fn, x)
wrapper() # Error: argument "x" is missing, with no defaultThe generated R wrapper tests missing() at the argument position of the
.Call():
wrapper <- function(x) .Call(rust_fn, if (missing(x)) quote(expr=) else x)quote(expr=) is an R idiom for the “missing” state: calling quote()
without its expr argument returns SEXP::missing_arg(). The call
proceeds, the sentinel passes through .Call(), and Missing<T> reads it
as Missing::Absent. Only the argument position may hold the sentinel: a
variable bound to it errors on lookup.
§A default on Missing<T>
#[miniextendr(default = "...")] on a Missing<T> parameter (or
defaults(x = "...") on a method) only writes the formal, so the usage
line shows the value the function stands for when the argument is left
out:
wrapper <- function(tol = 1e-8) .Call(rust_fn, if (missing(tol)) quote(expr=) else tol)missing(tol) is TRUE for an omitted argument even when the formal has
a default, so an omitted argument still reaches Rust as Missing::Absent
and the default is never evaluated. The Rust code decides what absence
means; the default in the formal documents it.
A supplied value is always Missing::Present, even one identical() to
the default: R’s missing(x) is FALSE once the caller supplies x, and
a caller who writes wrapper(tol = 1e-8) may mean that value. A caller
forwarding its own formal, function(tol = 1e-8) wrapper(tol), hands its
default over as a value too. This differs from a single-choice
match_arg / choices parameter under Missing<..>, whose formal is the
whole choice vector: that vector is never one valid choice, so passing it
whole reads as omitted. Such a choice parameter takes no default.
Enums§
- Missing
- Wrapper type that detects if an R argument was not passed (missing).
Functions§
- is_
missing_ arg - Check if a SEXP is the missing argument sentinel.