---
title: "Error Handling"
format:
html:
theme:
light: flatly
dark: [darkly, darkly-fixes.scss]
respect-user-color-scheme: true
format-links: false
vignette: >
%\VignetteIndexEntry{Charport Error Handling}
%\VignetteEngine{quarto::html}
%\VignetteEncoding{UTF-8}
---
## 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:
``` cpp
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:
``` cpp
#include
#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();
```
``` cpp
#include
#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:
``` cpp
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. |