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"