extendr_engine/
lib.rs

1//! Embeds a a single R process
2//!
3//! Using R's C-API requires the embedding of the R runtime.
4//! Thus, when using bindings in `libR-sys`, it is necessary that
5//! either the a R process is the caller, or that the process instantiates
6//! an accompanying R process. Otherwise, a run-time error occurs e.g.
7//! `(signal: 11, SIGSEGV: invalid memory reference)` or
8//!
9//! ```text
10//! Caused by:
11//! process didn't exit successfully: `/extendr/tests/extendrtest/target/debug/deps/extendrtest-59155c3c146ae614` (signal: 11, SIGSEGV: invalid memory reference)
12//! ```
13//!
14//! ## Testing
15//!
16//! Within tests, one must use [`test!`] or [`with_r`] as a wrapper around
17//! code that uses the R runtime, e.g.
18//!
19//! ```no_run
20//! #[test]
21//! fn testing_r_code() {
22//!     with_r(|| {
23//!     
24//!     });
25//! }
26//! ```
27//!
28//! Similarly with `test!` that is available in `extendr_api`, one may
29//!
30//! ```no_run   
31//! #[test]
32//! fn testing_r_code() {
33//!     test! {
34//!     
35//!     };
36//! }
37//! ```
38//!
39//! The advantage of `test!` is that it allows the use of `?` in test code, while
40//! `with_r` is not macro-based, thus code formatter `rustfmt` and rust LSPs (Rust Analyzer, Rust Rover, etc.)
41//! works within `with_r` without any problems.
42//!
43//!
44//! ## Binaries
45//!
46//! In a binary program, one may use [`start_r`] directly in the `main`-function.
47//!
48//! There is no `end_r`, as we terminate the R process setup, when the parent
49//! process terminates.
50//!
51//! [`test!`]: https://docs.rs/extendr-api/latest/extendr_api/macro.test.html
52//!
53// # Internal documentation
54//
55// ## Background
56//
57//
58// See [Rembedded.c](https://github.com/wch/r-source/blob/trunk/src/unix/Rembedded.c).
59//
60// [Rinside](https://github.com/eddelbuettel/rinside)
61//
62//
63
64use libR_sys::*;
65use std::os::raw;
66use std::sync::Once;
67
68// Generate mutable static strings.
69// Much more efficient than `CString`.
70// Generates asciiz.
71macro_rules! cstr_mut {
72    ($s: expr) => {
73        concat!($s, "\0").as_ptr() as *mut raw::c_char
74    };
75}
76
77static START_R: Once = Once::new();
78
79pub fn start_r() {
80    START_R.call_once(|| {
81        unsafe {
82            if std::env::var("R_HOME").is_err() {
83                // env! gets the build-time R_HOME stored by libR-sys
84                std::env::set_var("R_HOME", env!("R_HOME"));
85            }
86
87            // Due to Rf_initEmbeddedR using __libc_stack_end
88            // We can't call Rf_initEmbeddedR.
89            // Instead we must follow rustr's example and call the parts.
90
91            //let res = unsafe { Rf_initEmbeddedR(1, args.as_mut_ptr()) };
92            // NOTE: R will crash if this is called twice in the same process.
93            Rf_initialize_R(
94                3,
95                [cstr_mut!("R"), cstr_mut!("--slave"), cstr_mut!("--no-save")].as_mut_ptr(),
96            );
97
98            // In case you are curious.
99            // Maybe 8MB is a bit small.
100            // eprintln!("R_CStackLimit={:016x}", R_CStackLimit);
101            R_CStackLimit = usize::MAX;
102
103            setup_Rmainloop();
104        }
105    });
106}
107
108/// Close down the R interpreter. Note you won't be able to
109/// Restart it, so use with care or not at all.
110fn end_r() {
111    unsafe {
112        //Rf_endEmbeddedR(0);
113        R_RunExitFinalizers();
114        //CleanEd();
115        R_CleanTempDir();
116    }
117}
118
119/// Ensures that an embedded R instance is present when evaluating
120/// `f`.
121pub fn with_r(f: impl FnOnce()) {
122    start_r();
123    f();
124    // For compatibility with `test!` in `extendr-api/src/rmacros.rs`, there
125    // is no `end_r()` call here.
126}
127
128#[ctor::dtor]
129fn shutdown_r() {
130    if START_R.is_completed() {
131        end_r();
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_engine() {
141        // If this is the first call, it should wake up the interpreter.
142        start_r();
143
144        // This should do nothing.
145        start_r();
146
147        // Ending the interpreter is bad if we are running multiple threads.
148        // So avoid doing this in tests.
149        //end_r();
150    }
151}