Repository: extendr/extendr
Pull request #327 (2021-11-23 11:42:20)

Add Deref implementations for wrappers.
This PR adds derefs for wrapper types.

Vectors deref to slices of their value (`Rint /Rfloat/Rbool/Rstr/Robj`).

Some Vectors deref to mutable slices (`Rint/Rfloat/Rbool`) but not `Rstr` and `Robj` which need set_elt to work.

ExternalPtr<T> derefs to &T so we can use it like Box.

Rstr derefs to &str

We could deref Robj to &Rany - answers on a postcard.



andy-thomason: This will enable us to do:

```
fn fred(x: Integers) -> Rint {
   x[2]
}

fn fred(x: Integers) -> Rint {
   x[2]
}
```

yutannihilation: Looks very useful!

Patch:
diff --git a/extendr-api/src/lib.rs b/extendr-api/src/lib.rs
index 22f750db6..5fe9abde4 100644
--- a/extendr-api/src/lib.rs
+++ b/extendr-api/src/lib.rs
@@ -406,8 +406,8 @@ pub enum RType {
 }
 
 /// Enum use to unpack R objects into their specialist wrappers.
-/// Todo: convert all Robj types to wrappers.
-/// Note: this only works if the wrappers are all just SEXPs.
+// Todo: convert all Robj types to wrappers.
+// Note: this only works if the wrappers are all just SEXPs.
 #[derive(Debug, PartialEq)]
 pub enum Rany<'a> {
     Null(&'a Robj),               // NILSXP
diff --git a/extendr-api/src/scalar/macros.rs b/extendr-api/src/scalar/macros.rs
index 2066f62a9..6954a2512 100644
--- a/extendr-api/src/scalar/macros.rs
+++ b/extendr-api/src/scalar/macros.rs
@@ -659,13 +659,12 @@ macro_rules! gen_trait_impl {
         //     }
         // }
         paste::paste! {
-            // BUG: Disable NA tests until #321 is resolved
-            // #[doc = "```"]
-            // #[doc = "use extendr_api::prelude::*;"]
-            // #[doc = "test! {"]
-            // #[doc = "    assert!((<" $type ">::na()).is_na());"]
-            // #[doc = "}"]
-            // #[doc = "```"]
+            #[doc = "```"]
+            #[doc = "use extendr_api::prelude::*;"]
+            #[doc = "test! {"]
+            #[doc = "    assert!((<" $type ">::na()).is_na());"]
+            #[doc = "}"]
+            #[doc = "```"]
             impl CanBeNA for $type {
                 /// Return true is the is a NA value.
                 fn is_na(&self) -> bool {
diff --git a/extendr-api/src/scalar/rfloat.rs b/extendr-api/src/scalar/rfloat.rs
index 686c77ec5..950403943 100644
--- a/extendr-api/src/scalar/rfloat.rs
+++ b/extendr-api/src/scalar/rfloat.rs
@@ -9,6 +9,7 @@ use std::ops::{AddAssign, DivAssign, MulAssign, SubAssign};
 /// Rfloat has a special NA value, obtained from R headers via R_NaReal.
 ///
 /// Rfloat has the same footprint as an f64 value allowing us to use it in zero copy slices.
+#[repr(C)]
 pub struct Rfloat(pub f64);
 
 impl Rfloat {
@@ -30,11 +31,10 @@ impl Rfloat {
         self.0.is_subnormal()
     }
 }
+
 // `NA_real_` is a `NaN` with specific bit representation.
-// Check that underlying `f64` equals (bitwise) to `NA_real_`.
-gen_trait_impl!(Rfloat, f64, |x: &Rfloat| x.inner().is_na(), unsafe {
-    libR_sys::R_NaReal
-});
+// Check that underlying `f64` is `NA_real_`.
+gen_trait_impl!(Rfloat, f64, |x: &Rfloat| x.inner().is_na(), f64::na());
 gen_from_primitive!(Rfloat, f64);
 gen_from_scalar!(Rfloat, f64);
 gen_sum_iter!(Rfloat, 0f64);
diff --git a/extendr-api/src/wrapper/doubles.rs b/extendr-api/src/wrapper/doubles.rs
index aa20321d3..4be3a4264 100644
--- a/extendr-api/src/wrapper/doubles.rs
+++ b/extendr-api/src/wrapper/doubles.rs
@@ -92,3 +92,34 @@ mod tests {
         }
     }
 }
+
+// TODO: this should be a trait.
+impl Doubles {
+    pub fn set_elt(&mut self, index: usize, val: Rfloat) {
+        unsafe {
+            SET_REAL_ELT(self.get(), index as R_xlen_t, val.inner());
+        }
+    }
+}
+
+impl Deref for Doubles {
+    type Target = [Rfloat];
+
+    /// Treat Doubles as if it is a slice, like Vec<Rfloat>
+    fn deref(&self) -> &Self::Target {
+        unsafe {
+            let ptr = DATAPTR_RO(self.get()) as *const Rfloat;
+            std::slice::from_raw_parts(ptr, self.len())
+        }
+    }
+}
+
+impl DerefMut for Doubles {
+    /// Treat Doubles as if it is a mutable slice, like Vec<Rfloat>
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        unsafe {
+            let ptr = DATAPTR(self.get()) as *mut Rfloat;
+            std::slice::from_raw_parts_mut(ptr, self.len())
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/externalptr.rs b/extendr-api/src/wrapper/externalptr.rs
index 33837828b..6d81f7077 100644
--- a/extendr-api/src/wrapper/externalptr.rs
+++ b/extendr-api/src/wrapper/externalptr.rs
@@ -6,7 +6,7 @@ use std::any::Any;
 /// ```
 /// use extendr_api::prelude::*;
 /// test! {
-///     let extptr = ExternalPtr::from_val(1);
+///     let extptr = ExternalPtr::new(1);
 ///     assert_eq!(*extptr, 1);
 ///     let robj : Robj = extptr.into();
 ///     let extptr2 : ExternalPtr<i32> = robj.try_into().unwrap();
@@ -37,7 +37,9 @@ impl<T: Any> ExternalPtr<T> {
     /// In this case, the R object owns the data and will drop the Rust object
     /// when the last reference is removed via register_c_finalizer.
     ///
-    pub fn from_val(val: T) -> Self {
+    /// An ExternalPtr behaves like a Box except that the information is
+    /// tracked by a R object.
+    pub fn new(val: T) -> Self {
         unsafe {
             // This gets the type name of T as a string. eg. "i32".
             let type_name = std::any::type_name::<T>();
diff --git a/extendr-api/src/wrapper/integers.rs b/extendr-api/src/wrapper/integers.rs
index abc88786f..36386a46a 100644
--- a/extendr-api/src/wrapper/integers.rs
+++ b/extendr-api/src/wrapper/integers.rs
@@ -94,3 +94,34 @@ mod tests {
         }
     }
 }
+
+// TODO: this should be a trait.
+impl Integers {
+    pub fn set_elt(&mut self, index: usize, val: Rint) {
+        unsafe {
+            SET_INTEGER_ELT(self.get(), index as R_xlen_t, val.inner());
+        }
+    }
+}
+
+impl Deref for Integers {
+    type Target = [Rint];
+
+    /// Treat Integers as if it is a slice, like Vec<Rint>
+    fn deref(&self) -> &Self::Target {
+        unsafe {
+            let ptr = DATAPTR_RO(self.get()) as *const Rint;
+            std::slice::from_raw_parts(ptr, self.len())
+        }
+    }
+}
+
+impl DerefMut for Integers {
+    /// Treat Integers as if it is a mutable slice, like Vec<Rint>
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        unsafe {
+            let ptr = DATAPTR(self.get()) as *mut Rint;
+            std::slice::from_raw_parts_mut(ptr, self.len())
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/list.rs b/extendr-api/src/wrapper/list.rs
index 9708ccade..89057d1fa 100644
--- a/extendr-api/src/wrapper/list.rs
+++ b/extendr-api/src/wrapper/list.rs
@@ -395,3 +395,12 @@ impl<T: Into<Robj>> FromIterator<T> for List {
 }
 
 impl Attributes for List {}
+
+impl Deref for List {
+    type Target = [Robj];
+
+    /// Lists behave like slices of Robj.
+    fn deref(&self) -> &Self::Target {
+        self.as_slice()
+    }
+}
diff --git a/extendr-api/src/wrapper/macros.rs b/extendr-api/src/wrapper/macros.rs
index 68e26ad7f..c567c890f 100644
--- a/extendr-api/src/wrapper/macros.rs
+++ b/extendr-api/src/wrapper/macros.rs
@@ -82,8 +82,7 @@ macro_rules! gen_vector_wrapper_impl {
                 #[doc = "   let vec = " $type "::new(10);"]
                 #[doc = "   assert_eq!(vec.elt(0), <"$type_elem">::default());"]
                 #[doc = "   assert_eq!(vec.elt(9), <"$type_elem">::default());"]
-                // BUG: Disabled NA test until #321 is resolved
-                // #[doc = "   assert!(vec.elt(10).is_na());"]
+                #[doc = "   assert!(vec.elt(10).is_na());"]
                 #[doc = "}"]
                 #[doc = "```"]
                 pub fn elt(&self, index: usize) -> $type_elem {
diff --git a/extendr-api/src/wrapper/rstr.rs b/extendr-api/src/wrapper/rstr.rs
index ca52d8b4f..43dbdb671 100644
--- a/extendr-api/src/wrapper/rstr.rs
+++ b/extendr-api/src/wrapper/rstr.rs
@@ -12,7 +12,7 @@ use super::*;
 /// }
 /// ```
 ///
-#[derive(Debug, PartialEq, Clone)]
+#[derive(Debug, Clone)]
 pub struct Rstr {
     pub(crate) robj: Robj,
 }
@@ -60,3 +60,29 @@ impl From<&str> for Rstr {
         Rstr::from_string(s)
     }
 }
+
+impl Deref for Rstr {
+    type Target = str;
+
+    /// Treat Rstr like &str.
+    fn deref(&self) -> &Self::Target {
+        self.as_str()
+    }
+}
+
+impl<T> PartialEq<T> for Rstr
+where
+    T: AsRef<str>,
+{
+    /// Compare a `Rstr` with a `Rstr`.
+    fn eq(&self, other: &T) -> bool {
+        self.as_str() == other.as_ref()
+    }
+}
+
+impl PartialEq<str> for Rstr {
+    /// Compare a `Rstr` with a string slice.
+    fn eq(&self, other: &str) -> bool {
+        self.as_str() == other
+    }
+}
diff --git a/extendr-api/src/wrapper/strings.rs b/extendr-api/src/wrapper/strings.rs
index 81906af5e..c6f18cad2 100644
--- a/extendr-api/src/wrapper/strings.rs
+++ b/extendr-api/src/wrapper/strings.rs
@@ -133,3 +133,11 @@ where
         Strings::from_values([value.as_ref()])
     }
 }
+
+impl Deref for Strings {
+    type Target = [Rstr];
+
+    fn deref(&self) -> &Self::Target {
+        self.as_slice()
+    }
+}


Test patch:
diff --git a/extendr-api/tests/externalptr_test.rs b/extendr-api/tests/externalptr_test.rs
index 35c9be4fa..c22615b68 100644
--- a/extendr-api/tests/externalptr_test.rs
+++ b/extendr-api/tests/externalptr_test.rs
@@ -4,7 +4,7 @@ use lazy_static::lazy_static;
 #[test]
 fn test_externalptr() {
     test! {
-        let extptr = ExternalPtr::from_val(1);
+        let extptr = ExternalPtr::new(1);
         assert_eq!(*extptr, 1);
         let robj : Robj = extptr.into();
         let extptr2 : ExternalPtr<i32> = robj.try_into().unwrap();
@@ -34,7 +34,7 @@ fn test_externalptr_drop() {
         }
 
         // Create an external pointer to test the drop.
-        let extptr = ExternalPtr::from_val(X {});
+        let extptr = ExternalPtr::new(X {});
 
         // The object should be protected here - drop not called yet.
         R!("gc()").unwrap();
@@ -46,3 +46,17 @@ fn test_externalptr_drop() {
         assert_eq!(*Z.lock().unwrap(), true);
     }
 }
+
+#[test]
+fn test_externalptr_deref() {
+    test! {
+        struct X {
+            x: i32,
+            y: i32,
+        }
+
+        let extptr = ExternalPtr::new(X { x: 1, y: 2});
+        assert_eq!(extptr.x, 1);
+        assert_eq!(extptr.y, 2);
+    }
+}
diff --git a/extendr-api/tests/na_tests.rs b/extendr-api/tests/na_tests.rs
new file mode 100644
index 000000000..2a59b493a
--- /dev/null
+++ b/extendr-api/tests/na_tests.rs
@@ -0,0 +1,41 @@
+use extendr_api::prelude::*;
+
+#[test]
+fn test_float_na_is_na() {
+    test! {
+        assert!(f64::na().is_na());
+        assert!(Rfloat::na().is_na());
+    }
+}
+
+#[test]
+fn test_float_from_bits_is_na() {
+    test! {
+        let na_bits = 0x7ff00000u64 << 32 | 1954;
+        let na_r = Rfloat(unsafe {std::mem::transmute(na_bits)});
+        let na_f64 : f64 = unsafe {std::mem::transmute(na_bits)};
+        assert!(na_r.is_na());
+        assert!(na_f64.is_na());
+    }
+}
+
+#[test]
+fn test_float_not_na_is_not_na() {
+    test! {
+        assert!(!Rfloat(42f64).is_na());
+        assert!(!Rfloat(f64::NAN).is_na());
+        assert!(!Rfloat(f64::INFINITY).is_na());
+        assert!(!Rfloat(f64::NEG_INFINITY).is_na());
+        assert!(!Rfloat(f64::MAX).is_na());
+        assert!(!Rfloat(f64::MIN).is_na());
+        assert!(!Rfloat(f64::MIN_POSITIVE).is_na());
+
+        assert!(!42f64.is_na());
+        assert!(!f64::NAN.is_na());
+        assert!(!f64::INFINITY.is_na());
+        assert!(!f64::NEG_INFINITY.is_na());
+        assert!(!f64::MAX.is_na());
+        assert!(!f64::MIN.is_na());
+        assert!(!f64::MIN_POSITIVE.is_na());
+    }
+}
diff --git a/extendr-api/tests/vector_tests.rs b/extendr-api/tests/vector_tests.rs
index b43bfcfa0..e35734217 100644
--- a/extendr-api/tests/vector_tests.rs
+++ b/extendr-api/tests/vector_tests.rs
@@ -24,6 +24,10 @@ fn test_strings() {
         let s : Strings = ["x", "y", "z"].iter().collect();
         let v = s.iter().map(|c| c.as_str()).collect::<String>();
         assert_eq!(v, "xyz");
+        assert_eq!(&*s, &["x", "y", "z"]);
+
+        // Strings supports methods of &[Rstr] via Deref.
+        assert_eq!(s.contains(&"x".into()), true);
 
         let s = Strings::from_values(["x", <&str>::na(), "z"]);
         assert_eq!(s.elt(1).is_na(), true);
@@ -64,5 +68,126 @@ fn test_list() {
 
         let s = List::from_values(["x", <&str>::na(), "z"]);
         assert_eq!(s.elt(1)?.is_na(), true);
+        assert_eq!(s.as_slice().iter().any(|s| s.is_na()), true);
+
+        // Deref allows all the immutable methods from slice.
+        let v = s.as_slice().iter().map(|c| c).collect::<Vec<_>>();
+        assert_eq!(v, vec![&r!("x"), &r!(<&str>::na()), &r!("z")]);
+        assert_eq!(v[0], "x");
+        assert_eq!(v[1].is_na(), true);
+        assert_eq!(v.contains(&&r!("x")), true);
+        assert_eq!(s.as_slice().iter().any(Robj::is_na), true);
+    }
+}
+
+#[test]
+fn test_doubles() {
+    test! {
+        let s = Doubles::new(10);
+        assert_eq!(s.len(), 10);
+        assert_eq!(s.rtype(), RType::Real);
+
+        let mut s = Doubles::from_values([1, 2, 3]);
+        assert_eq!(s.len(), 3);
+        assert_eq!(s.rtype(), RType::Real);
+        assert_eq!(s.elt(0), 1.0);
+        assert_eq!(s.elt(1), 2.0);
+        assert_eq!(s.elt(2), 3.0);
+        assert!(s.elt(3).is_na());
+
+        let v = s.iter().collect::<Vec<Rfloat>>();
+        assert_eq!(v, [1.0, 2.0, 3.0]);
+
+        s.set_elt(1, 5.0.into());
+        assert_eq!(s.elt(1), 5.0);
+
+        let s : Doubles = [1.0, 2.0, 3.0].iter().map(|i| Rfloat::from(*i)).collect();
+        let v = s.iter().collect::<Doubles>();
+        assert_eq!(v, Doubles::from_values([1.0, 2.0, 3.0]));
+
+        // Bug: from_values should be Into<Rfloat>
+        //let s = Doubles::from_values([Rint::from(1), Rint::na(), Rint::from(3)]);
+        //assert_eq!(s.elt(1).is_na(), true);
+
+        let robj = r!([1.0, 2.0, 3.0]);
+        let s = Doubles::try_from(robj)?;
+        assert_eq!(s.len(), 3);
+        assert_eq!(s.elt(0), 1.0);
+
+        // Test Deref and DerefMut.
+        let mut s = Doubles::from_values([1.0, 2.0, 3.0]);
+        assert_eq!(s[0], 1.0);
+        assert_eq!(s[1], 2.0);
+        assert_eq!(s[2], 3.0);
+
+        s[0] = 4.0.into();
+        s[1] = 5.0.into();
+        s[2] = 6.0.into();
+        assert_eq!(s[0], 4.0);
+        assert_eq!(s[1], 5.0);
+        assert_eq!(s[2], 6.0);
+    }
+}
+
+#[test]
+fn test_integers() {
+    test! {
+        let s = Integers::new(10);
+        assert_eq!(s.len(), 10);
+        assert_eq!(s.rtype(), RType::Integer);
+
+        let mut s = Integers::from_values([1, 2, 3]);
+        assert_eq!(s.len(), 3);
+        assert_eq!(s.rtype(), RType::Integer);
+        assert_eq!(s.elt(0), 1);
+        assert_eq!(s.elt(1), 2);
+        assert_eq!(s.elt(2), 3);
+        assert!(s.elt(3).is_na());
+
+        let v = s.iter().collect::<Vec<Rint>>();
+        assert_eq!(v, [1, 2, 3]);
+
+        s.set_elt(1, 5.into());
+        assert_eq!(s.elt(1), 5);
+
+        let s : Integers = [1, 2, 3].iter().map(|i| Rint::from(*i)).collect();
+        let v = s.iter().collect::<Integers>();
+        assert_eq!(v, Integers::from_values([1, 2, 3]));
+
+        // Bug: from_values should be Into<Rint>
+        //let s = Integers::from_values([Rint::from(1), Rint::na(), Rint::from(3)]);
+        //assert_eq!(s.elt(1).is_na(), true);
+
+        let robj = r!([1, 2, 3]);
+        let s = Integers::try_from(robj)?;
+        assert_eq!(s.len(), 3);
+        assert_eq!(s.elt(0), 1);
+
+        // Test Deref and DerefMut.
+        let mut s = Integers::from_values([1, 2, 3]);
+        assert_eq!(s[0], 1);
+        assert_eq!(s[1], 2);
+        assert_eq!(s[2], 3);
+
+        s[0] = 4.into();
+        s[1] = 5.into();
+        s[2] = 6.into();
+        assert_eq!(s[0], 4);
+        assert_eq!(s[1], 5);
+        assert_eq!(s[2], 6);
+    }
+}
+
+#[test]
+fn test_rstr() {
+    test! {
+        let x = Rstr::from_string("xyz");
+        // All methods of &str are usable on Rstr.
+        assert_eq!(x.contains('y'), true);
+        assert_eq!(x.starts_with("xy"), true);
+        assert_eq!(x.len(), 3);
+
+        let x : Rstr = "xyz".into();
+        assert_eq!(x, "xyz");
     }
 }
diff --git a/tests/extendrtests/DESCRIPTION b/tests/extendrtests/DESCRIPTION
index ba938bf7c..6c5e499b4 100644
--- a/tests/extendrtests/DESCRIPTION
+++ b/tests/extendrtests/DESCRIPTION
@@ -14,7 +14,7 @@ SystemRequirements: Rust
 Encoding: UTF-8
 LazyData: true
 Roxygen: list(markdown = TRUE)
-RoxygenNote: 7.1.1
+RoxygenNote: 7.1.2
 Imports:
     brio,
     here
diff --git a/tests/extendrtests/R/extendr-wrappers.R b/tests/extendrtests/R/extendr-wrappers.R
index 6cc52181d..9bd6129af 100644
--- a/tests/extendrtests/R/extendr-wrappers.R
+++ b/tests/extendrtests/R/extendr-wrappers.R
@@ -34,6 +34,14 @@ doubles_square <- function(input) .Call(wrap__doubles_square, input)
 
 integers_square <- function(input) .Call(wrap__integers_square, input)
 
+try_rfloat_na <- function() .Call(wrap__try_rfloat_na)
+
+try_rint_na <- function() .Call(wrap__try_rint_na)
+
+check_rfloat_na <- function(x) .Call(wrap__check_rfloat_na, x)
+
+check_rint_na <- function(x) .Call(wrap__check_rint_na, x)
+
 #' Test whether `_arg` parameters are treated correctly in R
 #' Executes \code{`_x` - `_y`}
 #' @param _x an integer scalar, ignored
diff --git a/tests/extendrtests/src/Makevars b/tests/extendrtests/src/Makevars
index 3d7e33129..d955fe6df 100644
--- a/tests/extendrtests/src/Makevars
+++ b/tests/extendrtests/src/Makevars
@@ -1,5 +1,5 @@
-#LIBDIR = ./rust/target/release  # debug compiles faster
-LIBDIR = ./rust/target/debug
+TARGET_DIR = ./rust/target
+LIBDIR = $(TARGET_DIR)/debug
 STATLIB = $(LIBDIR)/libextendrtests.a
 PKG_LIBS = -L$(LIBDIR) -lextendrtests
 
@@ -8,8 +8,7 @@ all: C_clean
 $(SHLIB): $(STATLIB)
 
 $(STATLIB):
-#	cargo build --lib --release --manifest-path=./rust/Cargo.toml
-	cargo build --lib --manifest-path=./rust/Cargo.toml
+	cargo build --lib --manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR)
 
 C_clean:
 	rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS)
diff --git a/tests/extendrtests/src/Makevars.win b/tests/extendrtests/src/Makevars.win
index 2e1ab2574..0f6f5d787 100644
--- a/tests/extendrtests/src/Makevars.win
+++ b/tests/extendrtests/src/Makevars.win
@@ -1,14 +1,16 @@
 TARGET = $(subst 64,x86_64,$(subst 32,i686,$(WIN)))-pc-windows-gnu
-LIBDIR = ./rust/target/$(TARGET)/release
-STATLIB = $(LIBDIR)/libextendrtests.a
-PKG_LIBS = -L$(LIBDIR) -lextendrtests -lws2_32 -ladvapi32 -luserenv
+TOOLCHAIN = stable-msvc
+TARGET_DIR = ./rust/target
+LIBDIR = $(TARGET_DIR)/$(TARGET)/debug
+STATLIB = $(LIBDIR)/lextendrtest.a
+PKG_LIBS = -L$(LIBDIR) -lextendrtests -lws2_32 -ladvapi32 -luserenv -lbcrypt
 
 all: C_clean
 
 $(SHLIB): $(STATLIB)
 
 $(STATLIB):
-	cargo build --target=$(TARGET) --release --lib --manifest-path=./rust/Cargo.toml
+	cargo +$(TOOLCHAIN) build --target=$(TARGET) --lib --manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR)
 
 C_clean:
 	rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS)
diff --git a/tests/extendrtests/src/rust/Cargo.toml b/tests/extendrtests/src/rust/Cargo.toml
index 562b44c59..13a3b5923 100644
--- a/tests/extendrtests/src/rust/Cargo.toml
+++ b/tests/extendrtests/src/rust/Cargo.toml
@@ -20,6 +20,9 @@ extendr-api = "*"
 ## Replace by absolute path to simplify testing.
 ## CI overrides this path.
 extendr-api = { path = "../../../../../extendr/extendr-api"}
+## This allows to run `rcmdcheck` from `./tests/extendrtests/`
+# extendr-api = { path = "../../../../../../../../../extendr/extendr-api"}
+
 
 ## Build against current extendr version on github. Not recommended
 ## for development work.
diff --git a/tests/extendrtests/src/rust/src/lib.rs b/tests/extendrtests/src/rust/src/lib.rs
index 2eebf5a40..496725933 100644
--- a/tests/extendrtests/src/rust/src/lib.rs
+++ b/tests/extendrtests/src/rust/src/lib.rs
@@ -58,6 +58,27 @@ fn double_vec(x: Vec<f64>) -> Vec<f64> {
     x
 }
 
+// NA-related tests
+#[extendr(use_try_from = true)]
+fn try_rfloat_na() -> Rfloat {
+    Rfloat::na()
+}
+
+#[extendr(use_try_from = true)]
+fn try_rint_na() -> Rint {
+    Rint::na()
+}
+
+#[extendr(use_try_from = true)]
+fn check_rfloat_na(x : Rfloat) -> bool {
+    x.is_na()
+}
+
+#[extendr(use_try_from = true)]
+fn check_rint_na(x : Rint) -> bool {
+    x.is_na()
+}
+
 // Non-atomic types
 // TODO
 
@@ -224,6 +245,12 @@ extendr_module! {
     fn doubles_square;
     fn integers_square;
 
+    fn try_rfloat_na;
+    fn try_rint_na;
+
+    fn check_rfloat_na;
+    fn check_rint_na;
+
     fn special_param_names;
     fn __00__special_function_name;
 
diff --git a/tests/extendrtests/tests/testthat/test-na.R b/tests/extendrtests/tests/testthat/test-na.R
new file mode 100644
index 000000000..b26df6cf5
--- /dev/null
+++ b/tests/extendrtests/tests/testthat/test-na.R
@@ -0,0 +1,16 @@
+test_that("`extendr` returns correct `NA`s", {
+    expect_true(is.na(try_rfloat_na()))
+    expect_true(is.na(try_rint_na()))
+})
+
+test_that("`extendr` can check for `NA`", {
+    expect_true(check_rfloat_na(NA_real_))
+    expect_true(check_rint_na(NA_integer_))
+
+    expect_false(check_rfloat_na(NaN))
+    expect_false(check_rfloat_na(42))
+    expect_false(check_rfloat_na(Inf))
+    expect_false(check_rfloat_na(-Inf))
+
+    expect_false(check_rint_na(42L))
+})
\ No newline at end of file