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    #[deprecated(
48        since = "0.10.0",
49        note = "`NA` converts to `true`. Use `bool::try_from()` or `Option::<bool>::from()` instead"
50    )]
51    pub fn to_bool(&self) -> bool {
52        self.0 != 0
53    }
54
55    /// Convert this construct a `Rbool` from a rust boolean.
56    #[deprecated(since = "0.10.0", note = "Use `Rbool::from()` or `.into()` instead")]
57    pub fn from_bool(val: bool) -> Self {
58        Rbool(val as i32)
59    }
60}
61
62gen_trait_impl!(Rbool, bool, |x: &Rbool| x.0 == i32::MIN, i32::MIN);
63gen_from_primitive!(Rbool, i32);
64gen_partial_ord!(Rbool, bool);
65
66impl From<bool> for Rbool {
67    fn from(v: bool) -> Self {
68        Rbool(i32::from(v))
69    }
70}
71
72impl From<Option<bool>> for Rbool {
73    fn from(v: Option<bool>) -> Self {
74        if let Some(v) = v {
75            Rbool::from(v)
76        } else {
77            Rbool::na()
78        }
79    }
80}
81
82impl From<Rbool> for Option<bool> {
83    /// Convert an `Rbool` into an `Option<bool>`. `NA` becomes `None`.
84    fn from(v: Rbool) -> Self {
85        if v.is_na() {
86            None
87        } else {
88            Some(v.is_true())
89        }
90    }
91}
92
93impl TryFrom<Rbool> for bool {
94    type Error = Error;
95
96    /// Convert an `Rbool` into a `bool`. `NA` returns [`Error::MustNotBeNA`].
97    fn try_from(v: Rbool) -> Result<Self> {
98        if v.is_na() {
99            Err(Error::MustNotBeNA(v.into()))
100        } else {
101            Ok(v.is_true())
102        }
103    }
104}
105
106impl std::ops::Not for Rbool {
107    type Output = Self;
108
109    fn not(self) -> Self::Output {
110        if self.is_na() {
111            Rbool::na()
112        } else if self.is_true() {
113            Rbool::false_value()
114        } else {
115            Rbool::true_value()
116        }
117    }
118}
119
120impl std::fmt::Debug for Rbool {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        write!(
123            f,
124            "{}",
125            if self.is_na() {
126                "NA_LOGICAL"
127            } else if self.is_true() {
128                "TRUE"
129            } else {
130                "FALSE"
131            }
132        )
133    }
134}
135
136impl TryFrom<&Robj> for Rbool {
137    type Error = Error;
138
139    /// Convert an `LGLSXP` object into a `Rbool` (tri-state boolean).
140    /// Use `value.is_na()` to detect NA values.
141    fn try_from(robj: &Robj) -> Result<Self> {
142        if let Some(v) = robj.as_logical_slice() {
143            match v.len() {
144                0 => Err(Error::ExpectedNonZeroLength(robj.clone())),
145                1 => Ok(v[0]),
146                _ => Err(Error::ExpectedScalar(robj.clone())),
147            }
148        } else {
149            Err(Error::ExpectedLogical(robj.clone()))
150        }
151    }
152}