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}