Suppressing Call Stack Info in Rcpp-Generated Errors and Warnings

Michael Weylandt — written Mar 27, 2018 — source

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.

tags: basics  c++11 

Related Articles