extendr_api/na.rs
1use extendr_ffi::{R_IsNA, R_NaReal};
2use once_cell::sync::Lazy;
3use std::alloc::{self, Layout};
4
5// To make sure this "NA" is allocated at a different place than any other "NA"
6// strings (so that it can be used as a sentinel value), we allocate it by
7// ourselves.
8static EXTENDR_NA_STRING: Lazy<&'static str> = Lazy::new(|| unsafe {
9 // Layout::array() can fail when the size exceeds `isize::MAX`, but we
10 // only need 2 here, so it's safe to unwrap().
11 let layout = Layout::array::<u8>(2).unwrap();
12
13 // We allocate and never free it because we need this pointer to be
14 // alive until the program ends.
15 let ptr = alloc::alloc(layout);
16
17 let v: &mut [u8] = std::slice::from_raw_parts_mut(ptr, 2);
18 v[0] = b'N';
19 v[1] = b'A';
20
21 std::str::from_utf8_unchecked(v)
22});
23
24/// Return true if this primitive is `NA`.
25pub trait CanBeNA {
26 fn is_na(&self) -> bool;
27 fn na() -> Self;
28}
29
30/// ```
31/// use extendr_api::prelude::*;
32/// test! {
33/// assert!(f64::na().is_na());
34/// }
35/// ```
36impl CanBeNA for f64 {
37 fn is_na(&self) -> bool {
38 unsafe { R_IsNA(*self) != 0 }
39 }
40
41 fn na() -> f64 {
42 unsafe { R_NaReal }
43 }
44}
45
46/// ```
47/// use extendr_api::prelude::*;
48/// test! {
49/// assert!(i32::na().is_na());
50/// }
51/// ```
52impl CanBeNA for i32 {
53 fn is_na(&self) -> bool {
54 *self == i32::na()
55 }
56
57 fn na() -> i32 {
58 i32::MIN
59 }
60}
61
62/// Special "NA" string that represents null strings.
63/// ```
64/// use extendr_api::prelude::*;
65/// test! {
66/// assert_ne!(<&str>::na().as_ptr(), "NA".as_ptr());
67/// assert_eq!(<&str>::na(), "NA");
68/// assert_eq!("NA".is_na(), false);
69/// assert_eq!(<&str>::na().is_na(), true);
70/// }
71/// ```
72impl CanBeNA for &str {
73 /// Check for NA in a string by address.
74 fn is_na(&self) -> bool {
75 self.as_ptr() == <&str>::na().as_ptr()
76 }
77
78 fn na() -> Self {
79 &EXTENDR_NA_STRING
80 }
81}