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 provided by `extendr-ffi`, it is necessary that
5//! either an 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 extendr_ffi::{
65    setup_Rmainloop, R_CStackLimit, R_CleanTempDir, R_RunExitFinalizers, Rf_initialize_R,
66};
67use std::os::raw;
68use std::sync::Once;
69
70// Generate mutable static strings.
71// Much more efficient than `CString`.
72// Generates asciiz.
73macro_rules! cstr_mut {
74    ($s: expr) => {
75        concat!($s, "\0").as_ptr() as *mut raw::c_char
76    };
77}
78
79static START_R: Once = Once::new();
80
81pub fn start_r() {
82    START_R.call_once(|| {
83        unsafe {
84            if std::env::var("R_HOME").is_err() {
85                // env! gets the build-time R_HOME stored by extendr-ffi
86                std::env::set_var("R_HOME", env!("R_HOME"));
87            }
88
89            // Due to Rf_initEmbeddedR using __libc_stack_end
90            // We can't call Rf_initEmbeddedR.
91            // Instead we must follow rustr's example and call the parts.
92
93            //let res = unsafe { Rf_initEmbeddedR(1, args.as_mut_ptr()) };
94            // NOTE: R will crash if this is called twice in the same process.
95            Rf_initialize_R(
96                3,
97                [cstr_mut!("R"), cstr_mut!("--slave"), cstr_mut!("--no-save")].as_mut_ptr(),
98            );
99
100            // In case you are curious.
101            // Maybe 8MB is a bit small.
102            // eprintln!("R_CStackLimit={:016x}", R_CStackLimit);
103            R_CStackLimit = usize::MAX;
104
105            setup_Rmainloop();
106        }
107    });
108}
109
110/// Close down the R interpreter. Note you won't be able to
111/// Restart it, so use with care or not at all.
112fn end_r() {
113    unsafe {
114        //Rf_endEmbeddedR(0);
115        R_RunExitFinalizers();
116        //CleanEd();
117        R_CleanTempDir();
118    }
119}
120
121/// Ensures that an embedded R instance is present when evaluating
122/// `f`.
123pub fn with_r(f: impl FnOnce()) {
124    start_r();
125    f();
126    // For compatibility with `test!` in `extendr-api/src/rmacros.rs`, there
127    // is no `end_r()` call here.
128}
129
130#[ctor::dtor]
131fn shutdown_r() {
132    if START_R.is_completed() {
133        end_r();
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_engine() {
143        // If this is the first call, it should wake up the interpreter.
144        start_r();
145
146        // This should do nothing.
147        start_r();
148
149        // Ending the interpreter is bad if we are running multiple threads.
150        // So avoid doing this in tests.
151        //end_r();
152    }
153}