extendr_api/wrapper/dataframe.rs
1//! This provides an abstraction for R's `data.frame`-constructor in Rust.
2//! For a given `struct` say `CustomRow`, one may implement or derive [`IntoDataFrameRow`],
3//! thus being able to convert `Vec<CustomRow>` to an instance of `Dataframe<CustomRow>`,
4//! see [`Dataframe`].
5//!
6//!
7//! [`IntoDataFrameRow`]: ::extendr_macros::IntoDataFrameRow
8//!
9use super::*;
10
11/// A trait to convert a collection of `IntoDataFrameRow` into
12/// [`Dataframe`]. Typical usage involves using the derive-macro [`IntoDataFrameRow`]
13/// on a struct, which would generate `impl IntoDataframe<T> for Vec<T>`.
14///
15/// [`IntoDataFrameRow`]: ::extendr_macros::IntoDataFrameRow
16pub trait IntoDataFrameRow<T> {
17 fn into_dataframe(self) -> Result<Dataframe<T>>;
18}
19
20/// A representation of a typed `data.frame`
21///
22/// A `data.frame` can be created from Rust by using the [`IntoDataFrameRow`] trait
23/// which can be derived for a single `struct` that represents a single row.
24/// The type of the row is captured by the marker `T`.
25///
26/// Note that at present, you can create a `Dataframe<T>` but you cannot extract
27/// `T` from the object. `<T>` is purely a marker that indicates the struct that
28/// was used to create its rows.
29///
30/// As a result, using `Dataframe<T>` as a function argument _will not_ perform
31/// any type checking on the type.
32#[derive(PartialEq, Clone)]
33pub struct Dataframe<T> {
34 pub(crate) robj: Robj,
35 _marker: std::marker::PhantomData<T>,
36}
37
38impl<T> From<Dataframe<T>> for Robj {
39 fn from(value: Dataframe<T>) -> Self {
40 value.robj
41 }
42}
43
44impl<T> std::convert::TryFrom<&Robj> for Dataframe<T> {
45 type Error = Error;
46 fn try_from(robj: &Robj) -> Result<Self> {
47 // TODO: check type using derived trait.
48 if !(robj.is_list() && robj.inherits("data.frame")) {
49 return Err(Error::ExpectedDataframe(robj.clone()));
50 }
51 Ok(Dataframe {
52 robj: robj.clone(),
53 _marker: std::marker::PhantomData,
54 })
55 }
56}
57
58impl<T> std::convert::TryFrom<Robj> for Dataframe<T> {
59 type Error = Error;
60 fn try_from(robj: Robj) -> Result<Self> {
61 (&robj).try_into()
62 }
63}
64
65impl<T> Dataframe<T> {
66 /// Use `#[derive(IntoDataFrameRow)]` to use this.
67 pub fn try_from_values<I: IntoDataFrameRow<T>>(iter: I) -> Result<Self> {
68 iter.into_dataframe()
69 }
70}
71
72impl<T> Attributes for Dataframe<T> {}
73
74impl<T> std::fmt::Debug for Dataframe<T>
75where
76 T: std::fmt::Debug,
77{
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 write!(
80 f,
81 "dataframe!({})",
82 self.as_list()
83 .unwrap()
84 .iter()
85 .map(|(k, v)| if !k.is_empty() {
86 format!("{}={:?}", k, v)
87 } else {
88 format!("{:?}", v)
89 })
90 .collect::<Vec<_>>()
91 .join(", ")
92 )
93 }
94}
95
96impl<T> From<Option<Dataframe<T>>> for Robj {
97 fn from(value: Option<Dataframe<T>>) -> Self {
98 match value {
99 None => nil_value(),
100 Some(value) => value.into(),
101 }
102 }
103}