Unreleased (ReleaseDate)
v0.8.0 (2024-10-31)
Like anyhow, now you can use ?
to propagate any error that implements the
std::error::Error
trait.
#[savvy]
fn no_such_file() -> savvy::Result<()> {
// previously, you had to write .map_err(|e| e.to_string().into())
let _ = std::fs::read_to_string("no_such_file")?;
Ok(())
}
If you want to implement your own error type and the conversion to
savvy::Error
, please specify use-custom-error
feature to opt-out the
auto-conversion to avoid conflict with impl From<dyn std::error::Error> for savvy::Error
savvy = { version = "...", features = ["use-custom-error"] }
By introducing the anyhow-like conversion, savvy loses the error conversion from
a string (e.g. Err("foo".into())
). Instead, please use savvy_err!()
macro,
which is a shorthand of Error::new(format!(...))
.
#[savvy]
fn raise_error() -> savvy::Result<savvy::Sexp> {
Err(savvy_err!("This is my custom error"))
}
NumericScalar::as_usize()
andNumericSexp::iter_usize()
now rejects numbers larger than2^32
on 32-bit targets (i.e. webR). Thanks @eitsupi!
v0.7.2 (2024-10-27)
-
savvy now generates a reduced number of R functions.
-
savvy-cli now rejects a package name containing
.
.
v0.7.1 (2024-10-21)
NumericScalar::as_usize()
andNumericSexp::iter_usize()
now fail if the number is larger than2^53 - 1
because this is the maximum number that can be safely converted to usize.
v0.7.0 (2024-10-20)
Removed TryFrom<Sexp> for usize
, so the following code no longer compiles.
#[savvy]
fn foo(x: usize) -> savvy::Result<()> {
...
}
Instead, you can use i32
and convert it to usize
by yourself. If you are
sure the input number is never negative, you can just use the as
conversion.
If you are not sure, you should use <usize>::try_from()
and handle the error
by yourself. Also, please be aware you need to handle NA as well.
#[savvy]
fn foo(x: i32) -> savvy::Result<()> {
if x.is_na() {
return Err(savvy_err!("cannot convert NA to usize"));
}
let x = <usize>::try_from(x)?;
...
}
Alternatively, you can use newly-added methods, NumericScalar::as_usize()
and
NumericSexp::iter_usize()
. What's good is that this can handle integer-ish
numeric, which means you can allow users to input a larger number than the
integer max (2147483647)!
fn usize_to_string_scalar(x: NumericScalar) -> savvy::Result<Sexp> {
let x_usize = x.as_usize()?;
x_usize.to_string().try_into()
}
usize_to_string_scalar(2147483648)
#> [1] "2147483648"
v0.6.8 (2024-09-17)
savvy init
now generates- slightly better
configure
script that checks ifcargo
command is available cleanup
script to remove the generatedMakevars
after compilationconfigure.win
andcleanup.win
src/Makevars.win.in
instead ofsrc/Makevars.win
for consistency with Unix-alikes
- slightly better
v0.6.7 (2024-09-05)
-
Remove the use of non-API call
Rf_findVarInFrame
. -
Now the GitHub release includes an installation script to install the savvy CLI into
$CARGO_HOME/bin
, thanks to cargo-dist. This should make it easier to usesavvy-cli
on CI.curl --proto '=https' --tlsv1.2 -LsSf https://github.com/yutannihilation/savvy/releases/download/v0.6.7/savvy-cli-installer.sh | sh
- Improve handling of raw identifiers (e.g.
r#struct
) more.
v0.6.6 (2024-09-04)
-
Fix inappropriate use of
PROTECT
. Thanks @t-kalinowski! -
Handle raw identifiers (e.g.
r#struct
) correctly.
v0.6.5 (2024-07-02)
- Add support for raw, including ALTRAW.
Please be aware that, while this support was added for consistency, I bet it's really rare that a raw vector is actually needed; if you want to deal with a binary data on Rust's side, your primary option should be to store it in an external pointer (of a struct you define) rather than an R's raw vector.
-
Wrapper environment of a Rust struct or enum now cannot be modified by users.
-
Remove the use of the following non-API calls:
Rf_findVarInFrame3
STRING_PTR
DATAPTR
v0.6.4 (2024-05-25)
-
New function
r_warn()
safely show a warning. Note that, a warning can raise error whenoptions(warn = 2)
, so you should not ignore the error fromr_warn()
. The error should be propagated to the R session. -
Savvy now translates
Option<T>
as an optional argument, i.e., an argument with the default value ofNULL
.Example:
#[savvy] fn default_value_vec(x: Option<IntegerSexp>) -> savvy::Result<Sexp> { if let Some(x) = x { x.iter().sum::<i32>().try_into() } else { (-1).try_into() } }
default_value_vec(1:10) #> [1] 55 default_value_vec() #> [1] -1
r_print!()
andr_eprint!()
now can print strings containing%
.
- The notation for
savvy-cli test
is now changed to#[cfg(feature = "savvy-test")]
from#[cfg(savvy_test)]
. This is to avoid the upcoming change in Cargo (ref).
v0.6.3 (2024-05-05)
-
New types
NumericSexp
andNumericScalar
are added to handle both integer and double. You can get a slice viaas_slice_*()
or an iterator viaiter_*()
.#[savvy] fn times_two(x: NumericSexp) -> savvy::Result<Sexp> { let mut out = OwnedIntegerSexp::new(x.len())?; for (i, v) in x.iter_i32().enumerate() { let v = v?; // The item of iter_i32() is Result because the conversion can fail. if v.is_na() { out[i] = i32::na(); } else { out[i] = v * 2; } } out.into() }
You can also use
.into_typed()
to handle integer and double differently.#[savvy] fn times_two(x: NumericSexp) -> savvy::Result<savvy::Sexp> { match x.into_typed() { NumericTypedSexp::Integer(i) => times_two_int(i), NumericTypedSexp::Real(r) => times_two_real(r), } }
-
Savvy now provides
r_stdout()
andr_stderr()
to be used with interfaces that requirestd::io::Write
. Also, you can usesavvy::log::env_logger()
to output logs to R's stderr. Here's an example usage:use savvy::savvy_init; use savvy_ffi::DllInfo; #[savvy_init] fn init_logger(dll_info: *mut DllInfo) -> savvy::Result<()> { savvy::log::env_logger().init(); Ok(()) }
-
AltList
now losesnames
argument ininto_altrep()
for consistency. Please useset_names()
on the resultedSexp
object.let mut out = v.into_altrep()?; out.set_names(["one", "two"])?; Ok(out)
v0.6.2 (2024-05-04)
-
New macro
#[savvy_init]
makes the function executed when the DLL is loaded by R. This is useful for initializaing resources. See the guide for more details.Example:
use std::sync::OnceLock; static GLOBAL_FOO: OnceLock<Foo> = OnceLock::new(); #[savvy_init] fn init_global_foo(dll_info: *mut DllInfo) -> savvy::Result<()> { GLOBAL_FOO.get_or_init(|| Foo::new()); Ok(()) }
-
Savvy now experimentally supports ALTREP. See the guide for more details.
Example:
struct MyAltInt(Vec<i32>); impl MyAltInt { fn new(x: Vec<i32>) -> Self { Self(x) } } impl savvy::IntoExtPtrSexp for MyAltInt {} impl AltInteger for MyAltInt { const CLASS_NAME: &'static str = "MyAltInt"; const PACKAGE_NAME: &'static str = "TestPackage"; fn length(&mut self) -> usize { self.0.len() } fn elt(&mut self, i: usize) -> i32 { self.0[i] } }
v0.6.1 (2024-04-26)
- Now savvy no longer uses
SETLENGTH
, which is a so-called "non-API" thing.
v0.6.0 (2024-04-20)
-
savvy-cli test
now parses test modules marked with#[cfg(savvy_test)]
instead of#[cfg(test)]
. The purpose of this change is to letcargo test
run for the tests unrelated to a real R sessions. -
Savvy now generates different names of Rust functions and C functions; previously, the original function name is used for the FFI functions, but now it's
savvy_{original}_ffi
. This change shouldn't affect ordinary users.This change was necessary to let
#[savvy]
preserve the original function so that we can write unit tests on the function easily. One modification is that the function is made public. For more details, please read the Testing section in the guide. -
The generated R wrapper file is now named as
000-wrappers.R
instead ofwrappers.R
. This makes the file is loaded first so that you can override some of the R functions (e.g., aprint()
method for an enum) in another R file. The old wrapper filewrappers.R
is automatically deleted bysavvy-cli update
-
Added a function
eval_parse_text()
, which is an equivalent to R's idiomeval(parse(text = ))
. This is mainly for testing purposes. -
Added a function
is_r_identical()
, which is an equivalent to R'sidentical()
. This is mainly for testing purposes. -
Added a function
assert_eq_r_code()
if the first argument has the same data as the result of the R code of the second argument.Example:
let mut x = savvy::OwnedRealSexp::new(3)?; x[1] = 1.0; x[2] = 2.0; assert_eq_r_code(x, "c(0.0, 1.0, 2.0)");
-
savvy-cli test
now picks[dev-dependencies]
from the crate'sCargo.toml
as the dependencies to be used for testing. -
savvy-cli test
got--features
argument to add features to be used for testing.
v0.5.3 (2024-04-16)
-
Savvy now catches crash not only on the debug build, but also on the release build if
panic = "unwind"
. Instead, nowsavvy-cli init
generates aCargo.toml
with a release profile ofpanic = "abort"
. You need to modify this setting if you really want to catch panics on the release build. -
savvy-cli update
now ensures.Rbuildignore
contains^src/rust/.cargo$
and^src/rust/target$
. -
savvy-cli test
now uses OS's cache dir instead of the.savvy
directory.
- Now
savvy-cli test
works for other crates than savvy.
v0.5.2 (2024-04-14)
-
Now savvy's debug build (when
DEBUG
envvar is set totrue
, i.e.,devtools::load_all()
), panic doesn't crash R session and shows bactrace. This is useful for investigating what's the cause of the panic.Please keep in mind that, in Rust, panic is an unrecoverable error. So, not crashing doesn't mean you are saved.
-
savvy-cli test
no longer relies on the savvy R package.
-
Fixed a bug in
try_from_iter()
when the actual length is different than the size reported bysize_hint()
. -
savvy-cli test
now uses the local crate as the path dependency, instead of using the savvy crate fixedly.
v0.5.1 (2024-04-13)
-
An experimental new subcommand
savvy-cli test
runs tests by extracting and wrapping the test code with a temporary R package. This is because savvy always requires a real R session, which meanscargo test
doesn't work. Note that this relies on the savvy R package. Please install it before trying this.install.packages("savvy", repos = c("https://yutannihilation.r-universe.dev", "https://cloud.r-project.org"))
-
savvy-cli init
now generatesMakevars
that supports debug build whenDEBUG
envvar is set totrue
(i.e., indevtools::load_all()
).
v0.5.0 (2024-04-05)
-
To support enum properly (the details follow), now savvy requires to put
#[savvy]
macro also onstruct
.#[savvy] // NEW! struct Person { pub name: String, } #[savvy] impl Person {
This might be a bit inconvenient on the one hand, but, on the other hand, several good things are introduced by this change! See the New Features section.
-
Now
#[savvy]
macro supports enum to express the possible options for a parameter. This is useful when you want to let users specify some option without fear of typo. See the guide for more details.Example:
/// @export #[savvy] enum LineType { Solid, Dashed, Dotted, } /// @export #[savvy] fn plot_line(x: IntegerSexp, y: IntegerSexp, line_type: &LineType) -> savvy::Result<()> { match line_type { LineType::Solid => { ... }, LineType::Dashed => { ... }, LineType::Dotted => { ... }, } }
plot_line(x, y, LineType$Solid)
-
Savvy now allows
impl
definition over multiple files. It had been a headache that it wouldn't compile when you specified#[savvy]
onimpl
of a same struct multiple times. But now, you can split theimpl
not only within a same file but also over multiple files. -
OwnedListSexp
andListSexp
gainsunchecked_*()
variants of theset
andget
methods for a fast but unsafe operation. Thanks @daniellga!
v0.4.2 (2024-04-01)
-
OwnedIntegerSexp
and etc now havetry_from_iter()
method for constructing a new instance from an iterator.Example:
#[savvy] fn filter_integer_odd(x: IntegerSexp) -> savvy::Result<Sexp> { // is_na() is to propagate NAs let iter = x.iter().copied().filter(|i| i.is_na() || *i % 2 == 0); let out = OwnedIntegerSexp::try_from_iter(iter)?; out.into() }
-
OwnedIntegerSexp
and etc now havetry_from_slice()
method for constructing a new instance from a slice or vec. This conversion is and has been possible viatry_from()
, but this method was added for discoverability. -
OwnedIntegerSexp
and etc now havetry_from_scalar()
method for constructing a new instance from a scalar value (e.g.i32
). This conversion is and has been possible viatry_from()
, but this method was added for discoverability. -
savvy-cli update
andsavvy-cli init
now tries to parse the Rust files actually declared bymod
keyword.
v0.4.1 (2024-03-30)
Sexp
losesis_environment()
method becuase this isn't useful, considering savvy doesn't support environment.
-
get_dim()
andset_dim()
are now available also onSexp
. -
Now savvy allows to consume the value behind an external pointer. i.e.,
T
instead of&T
or&mut T
as the argument. After getting consumed, the pointer is null, so any function call on the already-consumed R object results in an error. See the guide for more details.Example:
struct Value {}; struct Wrapper { inner: Value } #[savvy] impl Value { fn new() -> Self { Self {} } } #[savvy] impl Wrapper { fn new(value: Value) -> Self { Self { inner: value } } }
v <- Value$new() w <- Wrapper$new(v) # value is consumed here. w <- Wrapper$new(v) #> Error: This external pointer is already consumed or deleted
-
Sexp
now hasassert_integer()
etc to verify the type of the underlying SEXP is as expected.
v0.4.0 (2024-03-27)
-
#[savvy]
on a struct'simpl
now generates the same name of R object that holds all the accociated functions. For example, previously the below code generates a constructorPerson()
, but now the constructor is available asPerson$new()
.struct Person { pub name: String, } /// @export #[savvy] impl Person { fn new() -> Self { Self { name: "".to_string(), } } }
- A struct marked with
#[savvy]
can be used as the return type of the associated function. In conjunction with the change in v0.3.0, now a user-defined struct can be used more flexibly than before. Please refer to the "Struct" section of the guide - An experimental support on complex is added under
compex
feature flag.ComplexSexp
andOwnedComplexSexp
are the corresponding Rust types. OwnedIntegerSexp
and etc now haveset_na(i)
method for shorthand ofset_elt(i, T::na())
. This is particularly useful forOwnedLogicalSexp
because its setter interfaceset_elt()
only acceptsbool
and no missing values.
- An expert-only method
new_without_init()
now skips initialization as intended.
v0.3.0 (2024-03-24)
-
Now user-defined struct can be used as an argument of
#[savvy]
-ed functions. It must be specified as&Ty
or&mut Ty
, notTy
.Example:
struct Person { pub name: String, } #[savvy] impl Person { fn get_name(&self) -> savvy::Result<savvy::Sexp> { let name = self.name.as_str(); name.try_into() } } #[savvy] fn get_name_external(x: &Person) -> savvy::Result<savvy::Sexp> { x.get_name() }
- Previously,
savvy-cli init
andsavvy-cli update
didn't handle the package name properly ("packageName" vs "package_name"). Now it's fixed.
- While this is described in the New features section, it was already allowed to
specify user-defined structs as argument if the user defines the necessary
TryFrom
implementations propoerly. At that time, specifying it without&
was possible, but now it's not allowed. Anyway, as this was undocumented and expert-only usage, I expect no one notices this breaking change.
v0.2.20 (2024-03-23)
v0.2.19 (2024-03-23)
LogicalSexp
andOwnedLogicalSexp
now haveas_slice_raw()
method. This is an expert-only function which might be found useful when you really need to distinguish NAs.
savvy-cli init
now generates<dllname>-win.def
to avoid the infamous "export ordinal too large" error on Windows.
v0.2.18 (2024-03-11)
- The version requirement is a bit more strict now.
v0.2.17 (2024-03-10)
get_dim()
now returns&[i32]
instead ofVec<usize>
to avoid allocation. If the matrix library requiresusize
, you need to convert thei32
tousize
by yourself now. Accordingly,set_dim()
now accepts both&[i32]
and&[usize]
.
v0.2.16 (2024-03-03)
fake-libR
feature is withdrawn. Instead, Windows users can add this build configuration to avoid the linker error:[target.x86_64-pc-windows-msvc] rustflags = ["-C", "link-arg=/FORCE:UNRESOLVED"]
v0.2.15 (2024-03-02)
- Previously, if a crate uses savvy,
cargo test
fails to compile on Windows even if the test code doesn't use the savvy API at all. This is because the symbols from Rinternals.h needs to be resolved. You can addsavvy
withfake-libR
feature indev-dependencies
to avoid this issue.
- Reject invalid external pointers so that the R session doesn't crash.
v0.2.14 (2024-02-14)
savvy-cli update
andsavvy-cli init
now correctly overwrite the existing files.
v0.2.13 (2024-02-14)
- The savvy-cli crate now requires Rust >= 1.74 because this is clap's MSRV.
v0.2.12 (2024-02-14)
savvy-cli init
now addsSystemRequirements
to theDESCRIPTION
file.
savvy-cli
now works if it's invoked outside of the R package directory.savvy-cli init
now generates the build configuration with a workaround for the case of thegnu
toolchain on Windows.
v0.2.11 (2024-02-04)
v0.2.10 (2024-02-04)
- Fix the wrong implementation of
to_vec()
.
v0.2.9 (2024-01-27)
Function.call()
now usesFunctionArgs
to represent function arguments. This is necessary change in order to protect function arguments from GC-ed unexpectedly. The previous interface requires users to passSexp
, which is unprotected.Function.call()
now doesn't require the environment to be executed because it rarely matters. Accordingly,Environment
is removed from the API.
v0.2.8 (2024-01-26)
-
savvy-cli init
now producesMakevars.in
andconfigure
instead ofMakevars
in order to support WebR transparently. One limitation on Windows is thatconfigure
cannot be set executable; you have to run the following command by yourself.git update-index --add --chmod=+x ./configure
- Add an experimental support for function and environment.
v0.2.7 (2024-01-25)
- (Experimentally) support WebR by not using
R_UnwindProtect()
.
v0.2.6 (2024-01-20)
- Fix misuses of
Rf_mkCharLenCE()
which caused compilation error on ARM64 Linux.
v0.2.5 (2024-01-20)
ListSexp
now returns anSexp
instead of aTypedSexp
. Use.into_typed()
to convert anSexp
to aTypedSexp
.
- Add
is_null()
. - Add
as_read_only()
toOwnedListSexp
as well. - Add
cast_unchecked()
andcast_mut_unchecked()
for casting an external pointer to a concrete type. Note that this is only needed for "external" external pointers.
v0.2.4 (2024-01-15)
v0.2.2 (2024-01-15)
r_print!
andr_eprint!
are now macro that wrapsformat!
, so you can use them just like Rust'sprint!
macro. There are alsor_println!
andr_eprintln!
available.
- Support scalar
usize
input. - Add methods to access and modify attributes:
get_attrib()
/set_attrib()
get_names()
/set_names()
get_class()
/set_class()
get_dim()
/set_dim()
- A struct marked with
#[savvy]
now hastry_from()
forSexp
.
- Newly-created R vectors (
Owned*Sexp
) are now properly initialized. If you really want to skip the initialization for some great reason, you can usenew_without_init()
instead ofnew()
. #[savvy]
now acceptssavvy::Sexp
as input.