Skip to main content

Module missing

Module missing 

Source
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> treats NULL as None and any other value as Some(T).
  • Missing<T> treats SEXP::missing_arg() as missing and any other value (including NULL) as present.

Use Missing<Option<T>> if you need to distinguish between:

  • Missing argument (not passed)
  • NULL (explicitly passed NULL)
  • 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)  # 5

However, .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 default

The 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.