Suppressing Call Stack Info in Rcpp-Generated Errors and Warnings
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
Introduction
Rcpp
has an elegant mechanism of exception handling
whereby C++
exceptions are automatically translated to errors in R
. For most
projects, the Rcpp::stop
wrapper (in conjunction with the BEGIN_RCPP
and
END_RCPP
macros automatically inserted by
RcppAttributes
)
is sufficient and easy to use, providing an Rcpp
equivalent of base::stop
.
By default, it captures the call stack and attaches it to the exception
in R
, giving informative error messages:
#include "Rcpp.h"
using namespace Rcpp;
//[[Rcpp::export]]
NumericVector add1(NumericVector x, NumericVector y){
if(x.size() != y.size()){
stop("x and y are not the same length!");
}
return x + y;
}
add1(1:5, 1:3)
Error in add1(1:5, 1:3): x and y are not the same length!
This matches the default behavior of base::stop()
which captures the call info.
For complex calling patterns (e.g., creating an argument list and calling the
Rcpp
function with do.call
), the resulting error messages are less helpful:
#include "Rcpp.h"
using namespace Rcpp;
// [[Rcpp::export]]
NumericVector internal_function_name(NumericVector x, NumericVector y){
if(x.size() != y.size()){
stop("x and y are not the same length!");
}
return x + y;
}
add2 <- function(x, y){
if(!is.numeric(x)){
x <- as.numeric(x)
}
do.call(internal_function_name, list(x, y))
}
add2(1:5, 1:3)
Error in (function (x, y) : x and y are not the same length!
If the internal error were being generated in R
code, we might choose to use
the call.=FALSE
argument to base::stop
to suppress the unhelpful (function (x, y)
part of the error message, but we don’t (immediately) have a corresponding option
in Rcpp
. In this gallery post, we show how to suppress the call-stack capture
of Rcpp::stop
to give cleaner error messages.
Error Messages
The key functionality was added to Rcpp
by Jim Hester in
Rcpp Pull Request #663.
To generate an R
-level exception without a call stack, we pass an optional
false
flag to Rcpp::exception
. For example,
#include "Rcpp.h"
using namespace Rcpp;
// [[Rcpp::export]]
NumericVector internal_function_name2(NumericVector x, NumericVector y){
if(x.size() != y.size()){
throw Rcpp::exception("x and y are not the same length!", false);
}
return x + y;
}
add3 <- function(x, y){
if(!is.numeric(x)){
x <- as.numeric(x)
}
do.call(internal_function_name2, list(x, y))
}
add3(1:5, 1:3)
Error: x and y are not the same length!
This can’t capture the R
level call stack, but it is at least cleaner than
the error message from the previous example.
Note that here, as elsewhere in C++
, we need to handle exceptions using a
try/catch
structure, but we do not add it explicitly because
RcppAttributes
automatically handles this for us.
Warnings
Similar to Rcpp::stop
, Rcpp
also provides a warning
function to generate
R
level warnings. It has the same call-stack capture behavior as stop
.
For the direct call case:
#include "Rcpp.h"
using namespace Rcpp;
//[[Rcpp::export]]
NumericVector add4(NumericVector x, NumericVector y){
if(x.size() != y.size()){
warning("x and y are not the same length!");
}
return x + y;
}
add4(1:5, 1:3)
Warning in add4(1:5, 1:3): x and y are not the same length! [1] 2 4 6 4 5
For the indirect call case:
#include "Rcpp.h"
using namespace Rcpp;
// [[Rcpp::export]]
NumericVector internal_function_name3(NumericVector x, NumericVector y){
if(x.size() != y.size()){
warning("x and y are not the same length!");
}
return x + y;
}
add5 <- function(x, y){
if(!is.numeric(x)){
x <- as.numeric(x)
}
do.call(internal_function_name3, list(x, y))
}
add5(1:5, 1:3)
Warning in (function (x, y) : x and y are not the same length! [1] 2 4 6 4 5
If we want to suppress the call stack info in this warning, we have to drop
down to the C
-level R
API. In particular, we use the Rf_warningcall
function,
which takes the call as the first argument. By passing a NULL
, we suppress the call:
#include "Rcpp.h"
using namespace Rcpp;
// [[Rcpp::export]]
NumericVector internal_function_name5(NumericVector x, NumericVector y){
if(x.size() != y.size()){
Rf_warningcall(R_NilValue, "x and y are not the same length!");
}
return x + y;
}
add6 <- function(x, y){
if(!is.numeric(x)){
x <- as.numeric(x)
}
do.call(internal_function_name5, list(x, y))
}
add6(1:5, 1)
Warning: x and y are not the same length! [1] 2 2 3 4 5
A C++11 Implementation
The above methods work, but they are not as clean as their Rcpp::stop
and
Rcpp::warning
counterparts. We can take advantage of C++11
to provide
similar functionality for our call-free versions.
Basing our implementation on the C++11
implementation
of Rcpp::stop
and Rcpp::warning
we can define our own
stopNoCall
and warningNoCall
#include "Rcpp.h"
using namespace Rcpp;
// [[Rcpp::plugins(cpp11)]]
template <typename... Args>
inline void warningNoCall(const char* fmt, Args&&... args ) {
Rf_warningcall(R_NilValue, tfm::format(fmt, std::forward<Args>(args)... ).c_str());
}
template <typename... Args>
inline void NORET stopNoCall(const char* fmt, Args&&... args) {
throw Rcpp::exception(tfm::format(fmt, std::forward<Args>(args)... ).c_str(), false);
}
// [[Rcpp::export]]
NumericVector internal_function_name6(NumericVector x, NumericVector y, bool warn){
if(x.size() != y.size()){
if(warn){
warningNoCall("x and y are not the same length!");
} else {
stopNoCall("x and y are not the same length!");
}
}
return x + y;
}
add7 <- function(x, y, warn=TRUE){
if(!is.numeric(x)){
x <- as.numeric(x)
}
do.call(internal_function_name6, list(x, y, warn))
}
add7(1:5, 1:3, warn=TRUE)
Warning: x and y are not the same length! [1] 2 4 6 4 5
add7(1:5, 1:3, warn=FALSE)
Error: x and y are not the same length!
Note that we used C++11
variadic templates here – if we wanted to do something
similar in C++98,
we could use essentially the same pattern, but would need to
implement each case individually.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.