Error handling

To propagate your errors to the R session, you can return a savvy::Error. savvy_err!() macro is a shortcut of savvy::Error::new(format!(...)) to create a new error.

use savvy::savvy_err;

#[savvy]
fn raise_error() -> savvy::Result<savvy::Sexp> {
    Err(savvy_err!("This is my custom error"))
}
raise_error()
#> Error: This is my custom error

Like anyhow, you can use ? to easily propagate any error that implements the std::error::Error trait.

#[savvy]
fn no_such_file() -> savvy::Result<()> {
    let _ = std::fs::read_to_string("no_such_file")?;
    Ok(())
}

Custom error

If you want to implement your own error type and the conversion to savvy::Error, it would conflict with the conversion of From<dyn std::error::Error>. To avoid an compile error, please sepcify use-custom-error feature to opt-out the conversion.

savvy = { version = "...", features = ["use-custom-error"] }

Show a warning

To show a warning, you can use r_warn().

savvy::io::r_warn("foo")?;

Note that, a warning can raise error when options(warn = 2), so you should not ignore the error from r_warn(). The error should be propagated to the R session.

Dealing with panic!

First of all, don't use panic!

If you are familiar with extendr, you might get used to use panic! casually. But, in the savvy framework, panic! crashes your R session. So, please don't use panic! directly. Also, please avoid operations that can cause panic! (e.g., unrwap()) when you are unsure.

This is because, in Rust, the meaning of panic! is an unrecoverable error. In theory, it's a sign that something impossible happens and there's no hope of recovery so there should be no way but to terminate the entire session. Savvy just respects what is supposed to happen.

But, if the session terminates immediately, it's hard to investigate the cause. What can I do?

Use debug build

If DEBUG envvar is set to true on building (i.e., devtools::load_all()), savvy catches panic! and shows the backtrace instead of crashing the R session.

For example, if you write this Rust function and load it by devtools::load_all(),

#[savvy]
fn must_panic() -> savvy::Result<()> {
    let x = &[1];
    let _ = x[1];  // Rust's index starts from 0!
    Ok(())
}

you'll see such an error like this with a backtrace instead of the RStudio bomb icon. You can check the line of the file suggested in the error message to guess what was happening.

must_panic()
#> panic occured!
#> 
#> Original message:
#>     panicked at src\error_handling.rs:33:13:
#>     index out of bounds: the len is 1 but the index is 1
#> 
#> Backtrace:
#>     ...
#>       18: std::panic::catch_unwind
#>                  at /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04\library\std\src/panic.rs:142:14
#>       19: simple_savvy::error_handling::savvy_must_panic_inner
#>                  at .\src\rust\src\error_handling.rs:30:1
#>       20: must_panic
#>                  at .\src\rust\src\error_handling.rs:30:1
#>       21: must_panic__impl
#>                  at .\src\init.c:291:16
#>     ...
#> 
#> note: Run with `RUST_BACKTRACE=1` for a full backtrace.
#> 
#> 
#> Error: panic happened

Set panic="unwind"

As described above, panic! is an unrecoverable error. It should not be recovered on the release build in principle.

That said, in some cases, panic! happens from the code out of your control. For example, if it is thrown by some of the dependency crates, there's litte you can do. You should report the author about the problem, but it's not always the behavior is fixed immediately and the fixed version is published. Also, keep in mind that depending on what originates the error, some authors can deliberately prefer to use panic! instead of Result. Note that panic! also happens in rust std library in situations such as division by zero or out-of-bounds error when indexing a Vec.

In such cases, you can change the following setting included in the template Cargo.toml generated by savvy-cli init. Set this to panic = "unwind" to gracefully convert a panic into an R error just like the debug build. Note that the backtrace is not available on the release build because there's no debug info.

[profile.release]
# ...snip...
panic = "unwind"