Skip to main content

extendr_api/scalar/
rbool.rs

1use crate::scalar::macros::*;
2use crate::*;
3use std::convert::TryFrom;
4
5/// `Rbool` is a wrapper for `i32` in the context of an R's logical vector.
6///
7/// `Rbool` can have a value of `0`, `1` or `i32::MIN`.
8///
9/// The value `i32::MIN` is used as `NA`.
10///
11/// `Rbool` has the same footprint as an `i32` value allowing us to use it in zero copy slices.
12#[repr(transparent)]
13#[readonly::make]
14pub struct Rbool(pub i32);
15
16impl Rbool {
17    pub fn new(val: i32) -> Self {
18        Rbool(val)
19    }
20
21    /// Return a `true` `Rbool`.
22    pub const fn true_value() -> Rbool {
23        Rbool(1)
24    }
25
26    /// Return a `false` `Rbool`.
27    pub const fn false_value() -> Rbool {
28        Rbool(0)
29    }
30
31    /// Return a `NA` `Rbool`.
32    pub const fn na_value() -> Rbool {
33        Rbool(i32::MIN)
34    }
35
36    /// Return `true` if this triboolean is `true` but not `NA`.
37    pub fn is_true(&self) -> bool {
38        self.0 != 0 && !self.is_na()
39    }
40
41    /// Return `true` if this triboolean is `false` but not `NA`.
42    pub fn is_false(&self) -> bool {
43        self.0 == 0 && !self.is_na()
44    }
45
46    /// Convert this `Rbool` to a bool. Note `NA` will be true.
47    pub fn to_bool(&self) -> bool {
48        self.0 != 0
49    }
50
51    /// Convert this construct a `Rbool` from a rust boolean.
52    pub fn from_bool(val: bool) -> Self {
53        Rbool(val as i32)
54    }
55}
56
57gen_trait_impl!(Rbool, bool, |x: &Rbool| x.0 == i32::MIN, i32::MIN);
58gen_from_primitive!(Rbool, i32);
59gen_partial_ord!(Rbool, bool);
60
61impl From<bool> for Rbool {
62    fn from(v: bool) -> Self {
63        Rbool(i32::from(v))
64    }
65}
66
67impl From<Option<bool>> for Rbool {
68    fn from(v: Option<bool>) -> Self {
69        if let Some(v) = v {
70            Rbool::from(v)
71        } else {
72            Rbool::na()
73        }
74    }
75}
76
77impl From<Rbool> for Option<bool> {
78    fn from(v: Rbool) -> Self {
79        if v.0.is_na() {
80            None
81        } else {
82            Some(v.0 != 0)
83        }
84    }
85}
86
87impl From<Rbool> for bool {
88    fn from(v: Rbool) -> Self {
89        v.0 != 0
90    }
91}
92
93impl std::ops::Not for Rbool {
94    type Output = Self;
95
96    fn not(self) -> Self::Output {
97        if self.is_na() {
98            Rbool::na()
99        } else if self.is_true() {
100            Rbool::false_value()
101        } else {
102            Rbool::true_value()
103        }
104    }
105}
106
107impl std::fmt::Debug for Rbool {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        write!(
110            f,
111            "{}",
112            if self.is_na() {
113                "NA_LOGICAL"
114            } else if self.is_true() {
115                "TRUE"
116            } else {
117                "FALSE"
118            }
119        )
120    }
121}
122
123impl TryFrom<&Robj> for Rbool {
124    type Error = Error;
125
126    /// Convert an `LGLSXP` object into a `Rbool` (tri-state boolean).
127    /// Use `value.is_na()` to detect NA values.
128    fn try_from(robj: &Robj) -> Result<Self> {
129        if let Some(v) = robj.as_logical_slice() {
130            match v.len() {
131                0 => Err(Error::ExpectedNonZeroLength(robj.clone())),
132                1 => Ok(v[0]),
133                _ => Err(Error::ExpectedScalar(robj.clone())),
134            }
135        } else {
136            Err(Error::ExpectedLogical(robj.clone()))
137        }
138    }
139}