extendr_api/io/
save.rs

1//! Wrapper for R output streams.
2
3use super::PstreamFormat;
4use crate::{catch_r_error, error::Error, error::Result, robj::GetSexp};
5use extendr_ffi::{
6    R_NilValue, R_Serialize, R_outpstream_st, R_outpstream_t, R_pstream_data_t, R_pstream_format_t,
7    SEXP,
8};
9use std::io::Write;
10
11/// The hook will convert some objects into strings.
12pub struct WriteHook {
13    pub func: unsafe extern "C" fn(arg1: SEXP, arg2: SEXP) -> SEXP,
14    pub data: SEXP,
15}
16
17pub struct OutStream<W: Write> {
18    r_state: R_outpstream_st,
19    writer: W,
20}
21
22impl<W: Write> OutStream<W> {
23    pub fn from_writer(
24        writer: W,
25        format: PstreamFormat,
26        version: i32,
27        hook: Option<WriteHook>,
28    ) -> Box<OutStream<W>> {
29        unsafe extern "C" fn outchar<W: Write>(arg1: R_outpstream_t, arg2: ::std::os::raw::c_int) {
30            let writer = &mut *((*arg1).data as *mut W);
31            let b = [arg2 as u8];
32            writer.write_all(&b).unwrap();
33        }
34
35        unsafe extern "C" fn outbytes<W: Write>(
36            arg1: R_outpstream_t,
37            arg2: *mut ::std::os::raw::c_void,
38            arg3: ::std::os::raw::c_int,
39        ) {
40            let writer = &mut *((*arg1).data as *mut W);
41            let b = std::slice::from_raw_parts(arg2 as *mut u8, arg3 as usize);
42            writer.write_all(b).unwrap();
43        }
44
45        {
46            let (hook_fn, hook_data) = if let Some(WriteHook { func, data }) = hook {
47                (Some(func), data)
48            } else {
49                unsafe { (None, R_NilValue) }
50            };
51
52            let r_state = extendr_ffi::R_outpstream_st {
53                data: std::ptr::null_mut(),
54                type_: format as R_pstream_format_t,
55                version,
56                OutChar: Some(outchar::<W>),
57                OutBytes: Some(outbytes::<W>),
58                OutPersistHookFunc: hook_fn,
59                OutPersistHookData: hook_data,
60            };
61            let mut os = Box::new(OutStream { r_state, writer });
62            os.r_state.data = &mut os.writer as *mut W as R_pstream_data_t;
63            os
64        }
65    }
66}
67
68pub trait Save: GetSexp {
69    /// Save an object in the R data format.
70    /// `version` should probably be 3.
71    fn save<P: AsRef<std::path::Path>>(
72        &self,
73        path: &P,
74        format: PstreamFormat,
75        version: i32,
76        hook: Option<WriteHook>,
77    ) -> Result<()> {
78        let mut writer = std::fs::File::create(path.as_ref())
79            .map_err(|_| Error::Other(format!("could not create file {:?}", path.as_ref())))?;
80        self.to_writer(&mut writer, format, version, hook)
81    }
82
83    /// Save an object in the R data format to a `Write` trait.
84    /// `version` should probably be 3.
85    fn to_writer<W: Write>(
86        &self,
87        writer: &mut W,
88        format: PstreamFormat,
89        version: i32,
90        hook: Option<WriteHook>,
91    ) -> Result<()> {
92        let mut os = OutStream::from_writer(writer, format, version, hook);
93
94        let stream = &mut os.r_state as R_outpstream_t;
95        let sexp = unsafe { self.get() };
96        if !(2..=3).contains(&version) {
97            return Err(Error::Other(format!(
98                "version must be 2 or 3, got {:?}",
99                version
100            )));
101        }
102
103        catch_r_error(move || unsafe {
104            R_Serialize(sexp, stream);
105            R_NilValue
106        })?;
107        Ok(())
108    }
109}
110
111impl<R: GetSexp> Save for R {}