#extension #lib #writting

nightly no-std rmin

A minimal Rust lib for writting R extensions

7 releases

0.4.3 Aug 2, 2024
0.4.3-pre0 Jul 19, 2024
0.4.0-pre2 Jun 9, 2024
0.3.1 Jun 6, 2024
0.1.0 May 31, 2024

#1360 in Rust patterns

Download history 22/week @ 2024-09-10 9/week @ 2024-09-17 17/week @ 2024-09-24 18/week @ 2024-10-01

672 downloads per month

AGPL-3.0

81KB
1K SLoC

rmin - A minimal Rust lib for writting R extensions

This is a very early version, only support vector type, and thus its overhead is minimized.

Compare to the well-knowned rextendr, This crate although with some limitations but could provide a faster implementation, a smaller code size, and a faster compile time.

Since it is small enough, you could vendor this crate easily into your CRAN package easily.

Status

The recent usable version is v0.4.3, DO NOT USE THE GIT VERSION

Please notice that, I am not familar with switching branches, the commit directly into the main branch is highly untrustable. Github is a repo only

Breaking changes in v0.4.3:

  • Create an (unsafe) type OptionSexp, since there is no guarateen makes MISSING(a missing value) in R returns non-zero (nor even returns, since the pointer may be invalid). This is not a choice, since with macro support, Sexp cannot missing,
  • unify the two entry with lib*.so and *.so in the macro: create both.
  • You should import with use rmin::{*, println}; in std mode, otherwise an warning is generated. Since std::println! cannot output to Rgui.exe, override std::println with an explicit import is needed.

Upcoming breaking changes in v0.4.0:

character re-bind to Sexp<char>, which has SEXPTYPE binds to STRSXP

The v0.3.0 character binding moves to Rchar, since R tells me the returned CHARSXP type has the name

adding an extra '%s\0' to printf and rf_errorcall, which prevent formating errors

Features

Need at least one of these feature: cfg-if(for no_std environment) or std(for normal usage).

Details:

panic-info-message

Enable rust feature panic_info_message, will bring Rust panic messages back to R, might be useful for debugging. Enabled by default.

std

Most of the rust crates are rely on std::*, if you want to use other crate, you should enable this feature. It takes ~1s compile the whole crate without lto, but if you enable lto for a faster executing speed, it might takes ~5s to finish compiling it.

core

A counter part for std, currently std is an indicator that just yields a warning while not correctly being specific correctly. This feature controls the linking of exception handling language items, and thus cannot be ignored when enable it.

rmin-macros

Import proc-macros #[export] fn func_name(...)...{...} and done!(crate_name) into crate::prelude and thus avaliable in crate::* root directly.

Notice that, macros require rmin::reg path to work (it is enabled automatically when choosing macros in rmin crate, if you enable rmin-macros as an independent dependency, you should enable rmin::reg manually.)

rmin-macros-camel-ass-wrapper

Internal use only, define the internal name with camel-ass naming method (aka iOS naming method) to avoid name collision.

rmin-macros-warning

Raise a warning with function with error (or empty) signature, for example. fn()->Owned<character> will yield a warning, [[]] is omitted since the signature is empty.

fn(a:Sexp<f64>,)->Owned<f64> also yields a same warning (due to the last comma)

They might harm the macro, thus raise an warning (although the 2 examples above are harmless, writting things like (a:Sexp<f64>,,b:Sexp<f64>) will interrupt the compile procedure.)

rmin-macros-verbose

Disable by default, contains some simple information such as the exported function name, and what the finalizer generates.

public-all

The most evil and dangerous feature. Better not to enable it. Most of the useful functions have a marker feature named public-by-default-even-public-all-is-not-set, that feature is a marker feature, do nothing but only tells you what function you could obtain from prelude module.

min-import

For prelude module. Since all the RType aliases could be access from crate::prelude::R, this feature disable import the aliases into prelude module.

register-routines

Register R routines, mainly for macros since hand writting such thing is painful.

cfg-if

Enable by default since compile the exception handling functtion for no_std environment need cfg-if. If you are using std feature, this could be disabled.

public-by-default-even-public-all-is-not-set

Dummy feature. Nothing happens if you disable it with --no-default-feature.

Note

Please switch to prelude module page for a first glance, since I want to show all docs, most of the private things are documented with a public-all feature flag. Please do not use them directly since most of them have a safe wrapper, and it is dangerous to use them directly.

Usage

Version 0.1.0 provides a fastest (but ugly) way to achieve about 2x speedup on with functions. They are discarded in 0.2.* since they are really unsafe and may cause memory leak.

The currently 0.3.0 version is slightly different from 0.2.0, which rename SEXP<T> to Sexp<T>, and (will) support things like Sexp<numeric_list>(R::numeric_list) or even an arbitrary list Sexp<(T1,T2)>.

Note: In the upcoming 0.4.0, all the decl_macro might be moved into a seperate crate which provide macros and proc_macros. This might only affect users with default no_std environment.

0.3.0, bring #[no_std] back!

In 0.3.0, feature std is optional again, which will give us a faster code generating speed.

Changes:

    • currently, new method and from (rust type) method goes to SExt, you could still write Owned<T>::new(), but a Protected<T> yields.
    • Move SEXP<T> to Sexp<T> thus SEXP and Sexp could be occur in the same situation
    • Using macro 2.0 to hide most of the struct and method from user interface, but remains the doc for debug purpose.
    • Adding support for lists (partially done.)

grammar

#![no_std]
use rmin::{*, println};
/// Return a+b to R.
#[no_mangle]
pub extern "C" fn add_protect(a:Sexp<f64>,b:Sexp<f64>) -> Owned<f64> {
handle_panic(||{
let mut c=Owned::new(1);
c[0]=a[0]+b[0];
c.into()
})
}
#[no_mangle]
pub extern "C" fn add_noprotect(a:Sexp<f64>,b:Sexp<f64>) -> Owned<f64> {
handle_panic(||{
let mut c=Owned::new(1);
c[0]=a[0]+b[0];
c.into()
})
}

/// raise panic.
#[no_mangle]
pub extern "C" fn panic() -> Owned<f64> {
handle_panic(||{
panic!("error occurs")
})
}

/// with macro
/// macro will register this function, thus R will check whether all parameters are missing
#[export]
fn macro_will_expand_and_register_it(a:Sexp<f64>)->Owned<f64>{
let mut b=Owned::new(1);
b[0]=a.data().sum();
}
done!();// in case you're using macros, adding a done! is necessary, this done call generate the
// register routine, which will ensure the expanded code is checked.
fn main() {} // just makes compiler happy

The program above could be tested with test command

export LOAD="dyn.load('target/release/examples/libcompare_rmin.so');addnp=getNativeSymbolInfo('add_noprotect');addp=getNativeSymbolInfo('add_protect');panic=getNativeSymbolInfo('panic')" ; LC_ALL=C r -e "$LOAD;system.time(sapply(1:100000,function(x)tryCatch(.Call(wrap__panic),error=I)))" 2>/dev/null ; LC_ALL=C r -e "$LOAD;system.time(sapply(1:1000000,function(x).Call(addp,1.,2.)));system.time(sapply(1:1000000,function(x).Call(addp,1.,2.)))"

Dependencies