Skip to contents

Overview

R packages with compiled code have several error mechanisms that do not always work well together: C++ exceptions, C-style errors, and R errors. No single mechanism is safe and idiomatic in every context. The design in charport does not try to put all of these error mechanisms into a single system, but instead tries to be parsimonious, only allowing errors to propagate based on the code and boundaries they interact with.

Operations that call R or construct R objects use R errors, because those operations can already fail through R. Pure C++ operations use standard C++ exceptions, while C access callbacks return C-style integer error codes.

Reader and Builder

Part C++ error handling C error handling
Reader construction Empty construction has no error. Reader(SEXP) uses R errors; with_rcpp() and with_cpp11() adapt construction to the framework’s C++ exception R error
Reader reset reset(SEXP) uses R errors. If resolution fails, the Reader keeps its current borrow. R error
Reader access Standard C++ exception Integer status
Reader destruction None None
Builder construction Standard C++ exception N/A
Builder string append Standard C++ exception N/A
Builder to_sexp() R error; to_sexp_with_rcpp() and to_sexp_with_cpp11() adapt it to the framework’s C++ exception N/A
charvec C constructor N/A R error; crosses the charport package boundary

An empty Reader can be initialized after its C++ lifetime has begun:

charport::Reader input;
input.reset(x);

reset() uses ordinary R error semantics. If resolution fails, the Reader keeps its current borrow. A successful reset releases that borrow and adopts the new one.

Rcpp and cpp11 adapters

Rcpp and cpp11 code can request framework adapters explicitly for Reader construction and Builder conversion:

#include <Rcpp.h>
#include "charport.h"

charport::Reader input = charport::Reader::with_rcpp(x);

charport::charvec::Builder output(input.size());
// Fill output.
SEXP result = output.to_sexp_with_rcpp();
#include <cpp11.hpp>
#include "charport.h"

charport::Reader input = charport::Reader::with_cpp11(x);

charport::charvec::Builder output(input.size());
// Fill output.
SEXP result = output.to_sexp_with_cpp11();

The framework header must come first. The Reader adapter protects the resolution operation, while the Builder adapter protects the terminal conversion to an R object. Each preserves the original R condition in the exception form expected by that framework.

Manual error handling

A C++ package that doesn’t use Rcpp or cpp11 must supply its own error handling around any R call for correctness, not just for charport but in general. R provides R_UnwindProtect() as the low-level mechanism for running cleanup during an R error.

Reader access exceptions

Status conversion is not a bounds check. Callers must supply nonnegative, in-range indices and sizes; an empty range may start at Reader::size(). Reader and the providers shipped with charport do not validate these bounds. A provider that chooses to validate may report CHARPORT_STATUS_OUT_OF_RANGE.

During access calls, e.g., Reader::views(), the Reader may be unable to provide those views. This is a return status code in C and an exception in C++.

C return status Meaning C++ exception
CHARPORT_STATUS_OK The output arrays were filled successfully. None
CHARPORT_STATUS_ERROR or any other nonzero status The access failed for another reason. std::runtime_error
CHARPORT_STATUS_NO_MEMORY Native allocation failed. std::bad_alloc
CHARPORT_STATUS_OUT_OF_RANGE The provider rejected an index or range. std::out_of_range

An access failure does not invalidate the Reader or make the error sticky. The provider state stays available for another access and for release. Whether a later access succeeds depends on the provider. When concurrent_access() is true, multiple worker threads can fail without sharing error state.

Builder conversion

charvec::Builder and its variants allocate and own their own Store during the C++ construction phase. Building the string data is standard C++ and may raise a C++ exception.

Builder::to_sexp() cannot throw a C++ exception, but since it creates the ALTREP object that contains the Store, it may theoretically raise an R allocation error. A caller that wants framework cleanup can use the named adapters:

SEXP rcpp_out = builder.to_sexp_with_rcpp();
SEXP cpp11_out = builder.to_sexp_with_cpp11();

Registration of ALTREP classes

Registering an ALTREP class requires several callbacks and lifecycle guarantees. A producer should meet the following contracts in order to make sure the consumer can always properly recover from errors and clean up resources.

Producer part Failure mechanism Producer obligation
init(SEXP) R error Any C++ exception must be converted to an R error.
Range and indexed access Integer status Should not call R. C++ exceptions should be converted to nonzero status codes. Return CHARPORT_STATUS_OK only after filling the outputs.
release(state) None If supplied, should not be able to produce an R error or C++ exception.