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}