libR_sys/
lib.rs

1//! A low-level libR binding library which is kept deliberately
2//! minimal.
3//!
4//! In particular, it has no external dependencies other that libR
5//! installed on the target.
6//!
7//! ## Synopsis
8//!
9//! The `libR-sys` crate is a low level bindgen wrapper for the R
10//! programming language. The intention is to allow one or more extension
11//! mechanisms to be implemented for rust.
12//!
13//! Effort to make the extension libraries platform-independent can be
14//! concentrated here.
15//!
16//! # Examples
17//!
18//! ```no_run
19//! use libR_sys::{Rf_initialize_R, R_CStackLimit, setup_Rmainloop};
20//! use std::os::raw;
21//!
22//! unsafe {
23//!   std::env::set_var("R_HOME", "/usr/lib/R");
24//!   let arg0 = "R\0".as_ptr() as *mut raw::c_char;
25//!   Rf_initialize_R(1, [arg0].as_mut_ptr());
26//!   R_CStackLimit = usize::max_value();
27//!   setup_Rmainloop();
28//! }
29//! ```
30//!
31//! # Conditional compilation depending on R installation
32//!
33//! The libR-sys crate provides these environmental variables that you can use in `build.rs`:
34//!
35//! - `DEP_R_R_VERSION_MAJOR`: The major part of the R version (e.g. `4` in version `4.1.0`)
36//! - `DEP_R_R_VERSION_MINOR`: The minor part of the R version (e.g. `1` in version `4.1.0`)
37//! - `DEP_R_R_VERSION_PATCH`: The patch part of the R version (e.g. `0` in version `4.1.0`)
38//! - `DEP_R_R_VERSION_DEVEL`: `true` if the R is a development version, otherwise `false`
39//! - `DEP_R_R_VERSION_STRING`: The full version string (e.g. `R version 4.1.0 (2021-05-18)`)
40//! - `DEP_R_R_HOME`: The R home directory
41//!
42//! ## Example `build.rs`
43//!
44//! ```ignore
45//! use std::env;
46//!
47//! fn main() {
48//!     // Set R_HOME envvar, and refer to it on compile time by env!("R_HOME")
49//!     let r_home = env::var("DEP_R_R_HOME").unwrap();
50//!     println!("cargo:rustc-env=R_HOME={}", r_home);
51//!
52//!     // Enable cfg setting to conditionally compile a code using a feature
53//!     // available only on newer versions of R
54//!     let major = env::var("DEP_R_R_VERSION_MAJOR").unwrap();
55//!     let minor = env::var("DEP_R_R_VERSION_MINOR").unwrap();
56//!     if &*major >= "4" && &*minor >= "1" {
57//!         println!("cargo:rustc-cfg=use_a_feature");
58//!     }
59//! }
60//! ```
61
62#![allow(non_upper_case_globals)]
63#![allow(non_camel_case_types)]
64#![allow(non_snake_case)]
65#![allow(improper_ctypes)]
66
67include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
68
69#[non_exhaustive]
70#[repr(transparent)]
71#[derive(Debug)]
72pub struct SEXPREC(std::ffi::c_void);
73
74extern "C" {
75    // Return type should match `SEXPTYPE`
76    pub fn TYPEOF(x: SEXP) -> SEXPTYPE;
77}
78
79#[allow(non_camel_case_types)]
80pub type R_altrep_Coerce_method_t =
81    ::std::option::Option<unsafe extern "C" fn(arg1: SEXP, arg2: SEXPTYPE) -> SEXP>;
82
83pub unsafe fn Rf_isS4(arg1: SEXP) -> Rboolean {
84    unsafe {
85        if secret::Rf_isS4_original(arg1) == 0 {
86            Rboolean::FALSE
87        } else {
88            Rboolean::TRUE
89        }
90    }
91}
92
93mod secret {
94    use super::*;
95    extern "C" {
96        #[link_name = "Rf_isS4"]
97        pub fn Rf_isS4_original(arg1: SEXP) -> u32;
98    }
99}
100
101impl From<Rboolean> for bool {
102    fn from(value: Rboolean) -> Self {
103        match value {
104            Rboolean::FALSE => false,
105            Rboolean::TRUE => true,
106        }
107    }
108}
109
110impl From<bool> for Rboolean {
111    fn from(value: bool) -> Self {
112        match value {
113            true => Rboolean::TRUE,
114            false => Rboolean::FALSE,
115        }
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use std::os::raw;
123
124    // Generate constant static strings.
125    // Much more efficient than CString.
126    // Generates asciiz.
127    macro_rules! cstr {
128        ($s: expr) => {
129            concat!($s, "\0").as_ptr() as *const raw::c_char
130        };
131    }
132
133    // Generate mutable static strings.
134    // Much more efficient than CString.
135    // Generates asciiz.
136    macro_rules! cstr_mut {
137        ($s: expr) => {
138            concat!($s, "\0").as_ptr() as *mut raw::c_char
139        };
140    }
141
142    // Thanks to @qinwf and @scottmmjackson for showing the way here.
143    fn start_R() {
144        unsafe {
145            if std::env::var("R_HOME").is_err() {
146                // env! gets the build-time R_HOME made in build.rs
147                std::env::set_var("R_HOME", env!("R_HOME"));
148            }
149
150            // Due to Rf_initEmbeddedR using __libc_stack_end
151            // We can't call Rf_initEmbeddedR.
152            // Instead we must follow rustr's example and call the parts.
153
154            //let res = unsafe { Rf_initEmbeddedR(1, args.as_mut_ptr()) };
155            if cfg!(target_os = "windows") && cfg!(target_arch = "x86") {
156                Rf_initialize_R(
157                    4,
158                    [
159                        cstr_mut!("R"),
160                        cstr_mut!("--arch=i386"),
161                        cstr_mut!("--slave"),
162                        cstr_mut!("--no-save"),
163                    ]
164                    .as_mut_ptr(),
165                );
166            } else {
167                Rf_initialize_R(
168                    3,
169                    [cstr_mut!("R"), cstr_mut!("--slave"), cstr_mut!("--no-save")].as_mut_ptr(),
170                );
171            }
172
173            // In case you are curious.
174            // Maybe 8MB is a bit small.
175            // eprintln!("R_CStackLimit={:016x}", R_CStackLimit);
176
177            if cfg!(not(target_os = "windows")) {
178                R_CStackLimit = usize::max_value();
179            }
180
181            setup_Rmainloop();
182        }
183    }
184
185    // Run some R code. Check the result.
186    #[test]
187    fn test_eval() {
188        start_R();
189        unsafe {
190            let val = Rf_protect(R_ParseEvalString(cstr!("1"), R_NilValue));
191            Rf_PrintValue(val);
192            assert_eq!(TYPEOF(val), SEXPTYPE::REALSXP);
193            assert_eq!(*REAL(val), 1.);
194            Rf_unprotect(1);
195        }
196        // There is one pathological example of `Rf_is*` where `TRUE` is not 1,
197        // but 16. We show here that the casting is done as intended
198        unsafe {
199            let sexp = R_ParseEvalString(cstr!(r#"new("factor")"#), R_GlobalEnv);
200            Rf_protect(sexp);
201            Rf_PrintValue(sexp);
202
203            assert_eq!(
204                std::mem::discriminant(&Rf_isS4(sexp)),
205                std::mem::discriminant(&Rboolean::TRUE),
206            );
207            assert!(<Rboolean as Into<bool>>::into(Rf_isS4(sexp)));
208            assert!(
209                (Rboolean::FALSE == Rf_isS4(sexp)) || (Rboolean::TRUE == Rf_isS4(sexp)),
210                "PartialEq implementation is broken"
211            );
212            assert!(Rboolean::TRUE == Rf_isS4(sexp));
213            assert_eq!(Rf_isS4(sexp), Rboolean::TRUE);
214            Rf_unprotect(1);
215        }
216    }
217}