Repository: extendr/extendr
Pull request #338 (2021-12-08 20:17:13)

Implement Logicals
This PR does the following:

Add Rbool as a type - Bool is still there but will be deprecated.
Add Logicals as a type.
Redesign the `gen_vector_wrapper_impl` macro a little, but keeping Ilia's original design.
Make sure everything works with scalar types including Integers and Doubles.
Implement ALTREP logicals support.
Add Logicals to Rany.
Fix *many* *many* tests.


Ilia-Kosenkov: What about a few trivial tests on R side here https://github.com/extendr/extendr/blob/master/tests/extendrtests/src/rust/src/lib.rs?
E.g., test for `NA` and `elt` access, similar to `Integer` and `Doubles`?
clauswilke: I think dropping `Bool` now is fine. The current version of extendr is basically a complete rewrite of the previous version anyways, so I don't see any reason to keep backwards compatibility.
andy-thomason: Good. I'll do this then.
andy-thomason: We may need to review the current case that:

`NA_LOGICAL != NA_LOGICAL`

Because `PartialEq` never returns true for NAs on Rbool, Rint or Rfloat.
Ilia-Kosenkov: > We may need to review the current case that:
> 
> `NA_LOGICAL != NA_LOGICAL`
> 
> Because `PartialEq` never returns true for NAs on Rbool, Rint or Rfloat.

Can you explain this? It seems that if `PartialEq` never returns `true` for `NA`, then `NA_LOGICAL != NA_LOGICAL`, or am I missing something? 
clauswilke: On the R side, you cannot compare an `NA` to an `NA`. The result is neither `TRUE` nor `FALSE` but rather `NA`. So returning `TRUE` when comparing `NA` to `NA` would definitely be problematic.
Ilia-Kosenkov: I am aware of this feature of R. Why can't we treat all `NA`s as floating-point `NaN`s, i.e. any equality operation involving an `NA` results in `false`, even `NA == NA`. This is somewhat consistent with R implementation, as I doubt anyone treats logical `NA` obtained during comparison as `TRUE`.
clauswilke: I agree, that's the most reasonable choice. It does imply `NA_LOGICAL != NA_LOGICAL`, though. Maybe I misunderstood what you were trying to say earlier. I though you considered it a problem.
Ilia-Kosenkov: I was trying to say `NA != NA` is the approach I prefer. Sorry for confusion. 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5ef59d688..4267553c4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,18 @@
 
 ## extendr devel
 
+- Removed Bool type.
+
+- Introduced scalars Rint, Rfloat and Rbool.
+
+- Introduced wrappers Integers, Doubles and Logicals.
+
+- Re-written impl Robj as a set of smaller traits.
+
+- Introduced Rany type and as_any.
+
+- Implemented Serialize for R objects.
+
 - Fixed Clippy warnings
 
 - Fixed "myvar" test failures - related to base_env() being the "local" enviroment in the tests.
diff --git a/extendr-api/src/lib.rs b/extendr-api/src/lib.rs
index 5fe9abde4..7f9d92650 100644
--- a/extendr-api/src/lib.rs
+++ b/extendr-api/src/lib.rs
@@ -226,7 +226,6 @@ pub mod error;
 pub mod functions;
 pub mod iter;
 pub mod lang_macros;
-pub mod logical;
 pub mod metadata;
 pub mod ownership;
 pub mod prelude;
@@ -262,7 +261,6 @@ pub use robj::Robj;
 pub use error::*;
 pub use functions::*;
 pub use lang_macros::*;
-pub use logical::*;
 pub use na::*;
 pub use rmacros::*;
 pub use robj::*;
@@ -275,15 +273,18 @@ pub use wrapper::*;
 pub use robj_ndarray::*;
 
 pub use extendr_macros::*;
+
+use scalar::Rbool;
+
 //////////////////////////////////////////////////
 
 pub struct Cplx(pub f64, pub f64);
 
 /// TRUE value eg. `r!(TRUE)`
-pub const TRUE: Bool = Bool(1);
+pub const TRUE: Rbool = Rbool::true_value();
 
 /// FALSE value eg. `r!(FALSE)`
-pub const FALSE: Bool = Bool(0);
+pub const FALSE: Rbool = Rbool::false_value();
 
 /// NULL value eg. `r!(NULL)`
 pub const NULL: () = ();
@@ -298,7 +299,7 @@ pub const NA_REAL: Option<f64> = None;
 pub const NA_STRING: Option<&str> = None;
 
 /// NA value for logical. `r!(NA_LOGICAL)`
-pub const NA_LOGICAL: Bool = Bool(std::i32::MIN);
+pub const NA_LOGICAL: Rbool = Rbool::na_value();
 
 #[doc(hidden)]
 pub use std::collections::HashMap;
@@ -420,7 +421,7 @@ pub enum Rany<'a> {
     Special(&'a Primitive),       // SPECIALSXP
     Builtin(&'a Primitive),       // BUILTINSXP
     Rstr(&'a Rstr),               // CHARSXP
-    Logical(&'a Robj),            // LGLSXP
+    Logical(&'a Logicals),        // LGLSXP
     Integer(&'a Integers),        // INTSXP
     Real(&'a Doubles),            // REALSXP
     Complex(&'a Robj),            // CPLXSXP
@@ -623,7 +624,7 @@ mod tests {
     }
 
     #[extendr]
-    pub fn bool_slice(x: &[Bool]) -> &[Bool] {
+    pub fn bool_slice(x: &[Rbool]) -> &[Rbool] {
         x
     }
 
@@ -774,7 +775,7 @@ mod tests {
                 assert_eq!(Robj::from_sexp(wrap__i32_slice(robj.get())), robj);
 
                 // #[extendr]
-                // pub fn bool_slice(x: &[Bool]) -> &[Bool] { x }
+                // pub fn bool_slice(x: &[Rbool]) -> &[Rbool] { x }
 
                 let robj = r!([TRUE, FALSE, TRUE]);
                 assert_eq!(Robj::from_sexp(wrap__bool_slice(robj.get())), robj);
diff --git a/extendr-api/src/logical.rs b/extendr-api/src/logical.rs
deleted file mode 100644
index a87ae26f7..000000000
--- a/extendr-api/src/logical.rs
+++ /dev/null
@@ -1,69 +0,0 @@
-/// Bool is a wrapper for i32 in the context of an R's tristate boolean.
-/// It can be TRUE, FALSE or NA_LOGICAL.
-#[derive(PartialEq, Eq)]
-pub struct Bool(pub i32);
-
-impl Bool {
-    /// Convert this Bool to a bool. Note NA will be true.
-    pub fn to_bool(&self) -> bool {
-        self.0 != 0
-    }
-
-    /// Convert this construct a Bool from a rust boolean.
-    pub fn from_bool(val: bool) -> Self {
-        Bool(val as i32)
-    }
-
-    /// Test if TRUE
-    pub fn is_true(&self) -> bool {
-        self.0 == 1
-    }
-
-    /// Test if FALSE
-    pub fn is_false(&self) -> bool {
-        self.0 == 0
-    }
-}
-
-impl Clone for Bool {
-    fn clone(&self) -> Self {
-        Self(self.0)
-    }
-}
-
-impl Copy for Bool {}
-
-impl From<i32> for Bool {
-    fn from(v: i32) -> Self {
-        Self(v)
-    }
-}
-
-impl From<bool> for Bool {
-    fn from(v: bool) -> Self {
-        Self(v as i32)
-    }
-}
-
-impl From<Bool> for bool {
-    fn from(v: Bool) -> Self {
-        v.0 != 0
-    }
-}
-
-impl From<&Bool> for bool {
-    fn from(v: &Bool) -> Self {
-        v.0 != 0
-    }
-}
-
-impl std::fmt::Debug for Bool {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Bool(0) => write!(f, "FALSE"),
-            Bool(1) => write!(f, "TRUE"),
-            Bool(std::i32::MIN) => write!(f, "NA_LOGICAL"),
-            _ => write!(f, "Bool({})", self.0),
-        }
-    }
-}
diff --git a/extendr-api/src/na.rs b/extendr-api/src/na.rs
index 98fdec936..070e67502 100644
--- a/extendr-api/src/na.rs
+++ b/extendr-api/src/na.rs
@@ -1,4 +1,3 @@
-use crate::Bool;
 use libR_sys::{R_IsNA, R_NaReal};
 /// Return true if this primitive is NA.
 pub trait CanBeNA {
@@ -38,22 +37,6 @@ impl CanBeNA for i32 {
     }
 }
 
-/// ```
-/// use extendr_api::prelude::*;
-/// test! {
-///     assert!(Bool::na().is_na());
-/// }
-/// ```
-impl CanBeNA for Bool {
-    fn is_na(&self) -> bool {
-        self.0 == Bool::na().0
-    }
-
-    fn na() -> Bool {
-        Bool::from(i32::MIN)
-    }
-}
-
 /// Special "NA" string that represents null strings.
 /// ```
 /// use extendr_api::prelude::*;
diff --git a/extendr-api/src/prelude.rs b/extendr-api/src/prelude.rs
index 17bd01b95..1b7c24a05 100644
--- a/extendr-api/src/prelude.rs
+++ b/extendr-api/src/prelude.rs
@@ -36,7 +36,6 @@ pub use crate::{
     data_frame, factor, global, list, r, reprint, reprintln, rprint, rprintln, sym, test, var,
 };
 
-pub use super::logical::Bool;
 pub use super::Cplx;
 
 pub use super::wrapper::{
@@ -59,7 +58,7 @@ pub use super::thread_safety::{
 
 pub use super::wrapper::{
     Doubles, EnvIter, Environment, Expression, ExternalPtr, FromList, Function, Integers, Language,
-    List, ListIter, Nullable, Pairlist, Primitive, Promise, Raw, Rstr, Strings, Symbol,
+    List, ListIter, Logicals, Nullable, Pairlist, Primitive, Promise, Raw, Rstr, Strings, Symbol,
 };
 
 #[cfg(feature = "ndarray")]
diff --git a/extendr-api/src/robj/into_robj.rs b/extendr-api/src/robj/into_robj.rs
index 294720f54..be2485aed 100644
--- a/extendr-api/src/robj/into_robj.rs
+++ b/extendr-api/src/robj/into_robj.rs
@@ -296,7 +296,7 @@ impl ToVectorValue for &bool {
     }
 }
 
-impl ToVectorValue for Bool {
+impl ToVectorValue for Rbool {
     fn sexptype() -> SEXPTYPE {
         LGLSXP
     }
@@ -305,11 +305,11 @@ impl ToVectorValue for Bool {
     where
         Self: Sized,
     {
-        self.0
+        self.inner()
     }
 }
 
-impl ToVectorValue for &Bool {
+impl ToVectorValue for &Rbool {
     fn sexptype() -> SEXPTYPE {
         LGLSXP
     }
@@ -318,7 +318,7 @@ impl ToVectorValue for &Bool {
     where
         Self: Sized,
     {
-        self.0
+        self.inner()
     }
 }
 
diff --git a/extendr-api/src/robj/mod.rs b/extendr-api/src/robj/mod.rs
index 25f07d98e..69490d126 100644
--- a/extendr-api/src/robj/mod.rs
+++ b/extendr-api/src/robj/mod.rs
@@ -14,7 +14,7 @@ use std::os::raw;
 
 use crate::*;
 
-use crate::scalar::{Rfloat, Rint};
+use crate::scalar::{Rbool, Rfloat, Rint};
 use std::collections::HashMap;
 use std::iter::IntoIterator;
 use std::ops::{Range, RangeInclusive};
@@ -374,29 +374,29 @@ impl Robj {
     }
 
     /// Get a read-only reference to the content of a logical vector
-    /// using the tri-state [Bool]. Returns None if not a logical vector.
+    /// using the tri-state [Rbool]. Returns None if not a logical vector.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let robj = r!([TRUE, FALSE, NA_LOGICAL]);
-    ///     assert_eq!(robj.as_logical_slice().unwrap(), [TRUE, FALSE, NA_LOGICAL]);
+    ///     let robj = r!([TRUE, FALSE]);
+    ///     assert_eq!(robj.as_logical_slice().unwrap(), [TRUE, FALSE]);
     /// }
     /// ```
-    pub fn as_logical_slice(&self) -> Option<&[Bool]> {
+    pub fn as_logical_slice(&self) -> Option<&[Rbool]> {
         self.as_typed_slice()
     }
 
-    /// Get a Vec<Bool> copied from the object
-    /// using the tri-state [Bool].
+    /// Get a Vec<Rbool> copied from the object
+    /// using the tri-state [Rbool].
     /// Returns None if not a logical vector.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let robj = r!([TRUE, FALSE, NA_LOGICAL]);
-    ///     assert_eq!(robj.as_logical_vector().unwrap(), vec![TRUE, FALSE, NA_LOGICAL]);
+    ///     let robj = r!([TRUE, FALSE]);
+    ///     assert_eq!(robj.as_logical_vector().unwrap(), vec![TRUE, FALSE]);
     /// }
     /// ```
-    pub fn as_logical_vector(&self) -> Option<Vec<Bool>> {
+    pub fn as_logical_vector(&self) -> Option<Vec<Rbool>> {
         self.as_logical_slice().map(|value| value.to_vec())
     }
 
@@ -405,25 +405,22 @@ impl Robj {
     /// use extendr_api::prelude::*;
     /// test! {
     ///     let robj = r!([TRUE, FALSE, NA_LOGICAL]);
-    ///     let (mut nt, mut nf, mut nna) = (0, 0, 0);
+    ///     let mut num_na = 0;
     ///     for val in robj.as_logical_iter().unwrap() {
-    ///       match *val {
-    ///         TRUE => nt += 1,
-    ///         FALSE => nf += 1,
-    ///         NA_LOGICAL => nna += 1,
-    ///         _ => ()
+    ///       if val.is_na() {
+    ///           num_na += 1;
     ///       }
     ///     }
-    ///     assert_eq!((nt, nf, nna), (1, 1, 1));
+    ///     assert_eq!(num_na, 1);
     /// }
     /// ```
-    pub fn as_logical_iter(&self) -> Option<impl Iterator<Item = &Bool>> {
+    pub fn as_logical_iter(&self) -> Option<impl Iterator<Item = &Rbool>> {
         self.as_logical_slice().map(|slice| slice.iter())
     }
 
     /// Get a read-only reference to the content of a double vector.
     /// Note: the slice may contain NaN or NA values.
-    /// We may introduce a "Real" type to handle this like the Bool type.
+    /// We may introduce a "Real" type to handle this like the Rbool type.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
@@ -637,12 +634,12 @@ impl Robj {
     /// ```
     pub fn as_bool(&self) -> Option<bool> {
         match self.as_logical_slice() {
-            Some(slice) if slice.len() == 1 && !slice[0].is_na() => Some(slice[0].into()),
+            Some(slice) if slice.len() == 1 && !slice[0].is_na() => Some(slice[0].is_true()),
             _ => None,
         }
     }
 
-    /// Get a scalar boolean as a tri-boolean [Bool] value.
+    /// Get a scalar boolean as a tri-boolean [Rbool] value.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
@@ -651,10 +648,10 @@ impl Robj {
     ///    let robj3 = Robj::from(NA_LOGICAL);
     ///    assert_eq!(robj1.as_logical(), Some(TRUE));
     ///    assert_eq!(robj2.as_logical(), None);
-    ///    assert_eq!(robj3.as_logical(), Some(NA_LOGICAL));
+    ///    assert_eq!(robj3.as_logical().unwrap().is_na(), true);
     /// }
     /// ```
-    pub fn as_logical(&self) -> Option<Bool> {
+    pub fn as_logical(&self) -> Option<Rbool> {
         match self.as_logical_slice() {
             Some(slice) if slice.len() == 1 => Some(slice[0]),
             _ => None,
@@ -770,7 +767,7 @@ macro_rules! make_typed_slice {
     }
 }
 
-make_typed_slice!(Bool, INTEGER, LGLSXP);
+make_typed_slice!(Rbool, INTEGER, LGLSXP);
 make_typed_slice!(i32, INTEGER, INTSXP);
 make_typed_slice!(Rint, INTEGER, INTSXP);
 make_typed_slice!(f64, REAL, REALSXP);
diff --git a/extendr-api/src/robj/try_from_robj.rs b/extendr-api/src/robj/try_from_robj.rs
index 692bc4a08..23bd53f62 100644
--- a/extendr-api/src/robj/try_from_robj.rs
+++ b/extendr-api/src/robj/try_from_robj.rs
@@ -99,10 +99,10 @@ impl_try_from_scalar_integer!(i64);
 impl_try_from_scalar_real!(f32);
 impl_try_from_scalar_real!(f64);
 
-impl TryFrom<Robj> for Bool {
+impl TryFrom<Robj> for Rbool {
     type Error = Error;
 
-    /// Convert an LGLSXP object into a Bool (tri-state boolean).
+    /// Convert an LGLSXP object into a Rbool (tri-state boolean).
     /// Use `value.is_na()` to detect NA values.
     fn try_from(robj: Robj) -> Result<Self> {
         if let Some(v) = robj.as_logical_slice() {
@@ -126,7 +126,7 @@ impl TryFrom<Robj> for bool {
         if robj.is_na() {
             Err(Error::MustNotBeNA(robj))
         } else {
-            Ok(<Bool>::try_from(robj)?.is_true())
+            Ok(<Rbool>::try_from(robj)?.is_true())
         }
     }
 }
@@ -195,10 +195,10 @@ impl TryFrom<Robj> for Vec<f64> {
     }
 }
 
-impl TryFrom<Robj> for Vec<Bool> {
+impl TryFrom<Robj> for Vec<Rbool> {
     type Error = Error;
 
-    /// Convert a LGLSXP object into a vector of Bool (tri-state booleans).
+    /// Convert a LGLSXP object into a vector of Rbool (tri-state booleans).
     /// Note: Unless you plan to store the result, use a slice instead.
     /// Use `value.is_na()` to detect NA values.
     fn try_from(robj: Robj) -> Result<Self> {
@@ -271,7 +271,7 @@ impl_option!(i32);
 impl_option!(i64);
 impl_option!(f32);
 impl_option!(f64);
-impl_option!(Bool);
+impl_option!(Rbool);
 impl_option!(bool);
 impl_option!(&str);
 impl_option!(String);
@@ -289,10 +289,10 @@ impl TryFrom<Robj> for &[i32] {
     }
 }
 
-impl TryFrom<Robj> for &[Bool] {
+impl TryFrom<Robj> for &[Rbool] {
     type Error = Error;
 
-    /// Convert a LGLSXP object into a slice of Bool (tri-state booleans).
+    /// Convert a LGLSXP object into a slice of Rbool (tri-state booleans).
     /// Use `value.is_na()` to detect NA values.
     fn try_from(robj: Robj) -> Result<Self> {
         robj.as_typed_slice().ok_or(Error::ExpectedLogical(robj))
diff --git a/extendr-api/src/robj_ndarray.rs b/extendr-api/src/robj_ndarray.rs
index d8413bc79..c0d244c55 100644
--- a/extendr-api/src/robj_ndarray.rs
+++ b/extendr-api/src/robj_ndarray.rs
@@ -41,7 +41,7 @@ macro_rules! make_array_view_2 {
     };
 }
 
-make_array_view_2!(Bool, "Not a logical matrix.");
+make_array_view_2!(Rbool, "Not a logical matrix.");
 make_array_view_2!(i32, "Not an integer matrix.");
 make_array_view_2!(f64, "Not a floating point matrix.");
 //make_array_view_2!(u8, "Not a raw matrix.");
@@ -106,8 +106,8 @@ fn test_from_robj() {
             Ok(ArrayView1::<i32>::from(&[1][..]))
         );
         assert_eq!(
-            <ArrayView1<Bool>>::from_robj(&Robj::from(true)),
-            Ok(ArrayView1::<Bool>::from(&[Bool(1)][..]))
+            <ArrayView1<Rbool>>::from_robj(&Robj::from(true)),
+            Ok(ArrayView1::<Rbool>::from(&[TRUE][..]))
         );
 
         let robj = R!("matrix(c(1, 2, 3, 4, 5, 6, 7, 8), ncol=2, nrow=4)")?;
@@ -142,7 +142,7 @@ fn test_from_robj() {
 
         // check logical matrices
         let robj = R!("matrix(c(T, T, T, T, F, F, F, F), ncol=2, nrow=4)")?;
-        let mx = <ArrayView2<Bool>>::from_robj(&robj)?;
+        let mx = <ArrayView2<Rbool>>::from_robj(&robj)?;
         assert_eq!(mx[[0, 0]], TRUE);
         assert_eq!(mx[[1, 0]], TRUE);
         assert_eq!(mx[[2, 0]], TRUE);
diff --git a/extendr-api/src/scalar/macros.rs b/extendr-api/src/scalar/macros.rs
index 6954a2512..25b82da5f 100644
--- a/extendr-api/src/scalar/macros.rs
+++ b/extendr-api/src/scalar/macros.rs
@@ -725,6 +725,29 @@ macro_rules! gen_trait_impl {
             }
         }
 
+        // The 'example usage' expands to...
+        //
+        // /// Documentation comments/test built by the #[doc] attributes
+        // impl PartialEq<i32> for Rint {
+        //     fn eq(&self, other: &i32) -> bool {
+        //         !self.is_na() && self.0 == *other
+        //     }
+        // }
+        paste::paste! {
+            #[doc = "```"]
+            #[doc = "use extendr_api::prelude::*;"]
+            #[doc = "test! {"]
+            #[doc = "    assert!(<" $type ">::default().eq(&<" $type_prim ">::default()));"]
+            #[doc = "}"]
+            #[doc = "```"]
+            impl PartialEq<$type_prim> for &$type {
+                /// NA always fails.
+                fn eq(&self, other: &$type_prim) -> bool {
+                    <Option<$type_prim>>::try_from(**self) == Ok(Some(*other))
+                }
+            }
+        }
+
         // The 'example usage' expands to...
         //
         // /// Documentation comments/test built by the #[doc] attributes
@@ -743,7 +766,7 @@ macro_rules! gen_trait_impl {
             impl PartialEq<$type_prim> for $type {
                 /// NA always fails.
                 fn eq(&self, other: &$type_prim) -> bool {
-                    !self.is_na() && self.0 == *other
+                    <Option<$type_prim>>::try_from(self.clone()) == Ok(Some(*other))
                 }
             }
         }
@@ -760,12 +783,12 @@ macro_rules! gen_trait_impl {
             #[doc = "```"]
             #[doc = "use extendr_api::prelude::*;"]
             #[doc = "test! {"]
-            #[doc = "    assert_eq!(<" $type ">::default().0, <" $type_prim ">::default());"]
+            #[doc = "    assert_eq!(<" $type ">::default(), <" $type_prim ">::default());"]
             #[doc = "}"]
             #[doc = "```"]
             impl std::default::Default for $type {
                 fn default() -> Self {
-                    $type(<$type_prim>::default())
+                    $type::from(<$type_prim>::default())
                 }
             }
         }
diff --git a/extendr-api/src/scalar/mod.rs b/extendr-api/src/scalar/mod.rs
index bcc09d164..451b1dbc5 100644
--- a/extendr-api/src/scalar/mod.rs
+++ b/extendr-api/src/scalar/mod.rs
@@ -1,5 +1,7 @@
 mod macros;
+mod rbool;
 mod rfloat;
 mod rint;
+pub use rbool::Rbool;
 pub use rfloat::Rfloat;
 pub use rint::Rint;
diff --git a/extendr-api/src/scalar/rbool.rs b/extendr-api/src/scalar/rbool.rs
new file mode 100644
index 000000000..807bd03a3
--- /dev/null
+++ b/extendr-api/src/scalar/rbool.rs
@@ -0,0 +1,94 @@
+use crate::scalar::macros::*;
+use crate::*;
+use std::convert::TryFrom;
+
+/// Rbool is a wrapper for i32 in the context of an R's logical vector.
+///
+/// Rbool can have a value of 0, 1 or i32::MIN.
+///
+/// The value i32::MIN is used as "NA".
+///
+/// Rbool has the same footprint as an i32 value allowing us to use it in zero copy slices.
+pub struct Rbool(i32);
+
+impl Rbool {
+    gen_impl!(Rbool, i32);
+
+    /// Return a `true` `Rbool`.
+    pub const fn true_value() -> Rbool {
+        Rbool(1)
+    }
+
+    /// Return a `false` `Rbool`.
+    pub const fn false_value() -> Rbool {
+        Rbool(0)
+    }
+
+    /// Return a `NA` `Rbool`.
+    pub const fn na_value() -> Rbool {
+        Rbool(i32::MIN)
+    }
+
+    /// Return `true` if this triboolean is `true` but not NA.
+    pub fn is_true(&self) -> bool {
+        self.inner() != 0 && !self.is_na()
+    }
+
+    /// Return `true` if this triboolean is `false` but not NA.
+    pub fn is_false(&self) -> bool {
+        self.inner() == 0 && !self.is_na()
+    }
+
+    /// Convert this Rbool to a bool. Note NA will be true.
+    pub fn to_bool(&self) -> bool {
+        self.inner() != 0
+    }
+
+    /// Convert this construct a Rbool from a rust boolean.
+    pub fn from_bool(val: bool) -> Self {
+        Rbool(val as i32)
+    }
+}
+
+gen_trait_impl!(Rbool, bool, |x: &Rbool| x.inner() == i32::MIN, i32::MIN);
+gen_from_primitive!(Rbool, i32);
+
+impl From<bool> for Rbool {
+    fn from(v: bool) -> Self {
+        Rbool(if v { 1 } else { 0 })
+    }
+}
+
+impl From<Option<bool>> for Rbool {
+    fn from(v: Option<bool>) -> Self {
+        if let Some(v) = v {
+            Rbool::from(v)
+        } else {
+            Rbool::na()
+        }
+    }
+}
+
+impl From<Rbool> for Option<bool> {
+    fn from(v: Rbool) -> Self {
+        if v.inner().is_na() {
+            None
+        } else {
+            Some(v.inner() != 0)
+        }
+    }
+}
+
+impl std::ops::Not for Rbool {
+    type Output = Self;
+
+    fn not(self) -> Self::Output {
+        if self.is_na() {
+            Rbool::na()
+        } else if self.is_true() {
+            Rbool::false_value()
+        } else {
+            Rbool::true_value()
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/altrep.rs b/extendr-api/src/wrapper/altrep.rs
index 9dd69ac35..a2074c096 100644
--- a/extendr-api/src/wrapper/altrep.rs
+++ b/extendr-api/src/wrapper/altrep.rs
@@ -1,23 +1,23 @@
-use prelude::{Rfloat, Rint};
+use prelude::{Rbool, Rfloat, Rint};
 
 use super::*;
 
 macro_rules! make_from_iterator {
-    ($fn_name : ident, $make_class : ident, $impl : ident, $r_type : ident, $prim_type : ty) => {
+    ($fn_name : ident, $make_class : ident, $impl : ident, $scalar_type : ident, $prim_type : ty) => {
         pub fn $fn_name<Iter>(iter: Iter) -> Altrep
         where
             Iter: ExactSizeIterator + std::fmt::Debug + Clone + 'static + std::any::Any,
-            Iter::Item: Into<$prim_type>,
+            Iter::Item: Into<$scalar_type>,
         {
             impl<Iter: ExactSizeIterator + std::fmt::Debug + Clone> $impl for Iter
             where
-                Iter::Item: Into<$prim_type>,
+                Iter::Item: Into<$scalar_type>,
             {
-                fn elt(&self, index: usize) -> $r_type {
-                    $r_type(self.clone().nth(index).unwrap().into())
+                fn elt(&self, index: usize) -> $scalar_type {
+                    $scalar_type::from(self.clone().nth(index).unwrap().into())
                 }
 
-                fn get_region(&self, index: usize, data: &mut [$r_type]) -> usize {
+                fn get_region(&self, index: usize, data: &mut [$scalar_type]) -> usize {
                     let len = self.len();
                     if index > len {
                         0
@@ -26,7 +26,7 @@ macro_rules! make_from_iterator {
                         let num_elems = data.len().min(len - index);
                         let dest = &mut data[0..num_elems];
                         for d in dest.iter_mut() {
-                            *d = $r_type(iter.next().unwrap().into());
+                            *d = $scalar_type::from(iter.next().unwrap().into());
                         }
                         num_elems
                     }
@@ -190,9 +190,9 @@ pub trait AltIntegerImpl: AltrepImpl {
         for i in 0..len {
             let val = self.elt(i);
             if !val.is_na() {
-                tot += val.0 as i64;
-                min = min.min(val.0);
-                max = max.max(val.0);
+                tot += val.inner() as i64;
+                min = min.min(val.inner());
+                max = max.max(val.inner());
                 nas += 1;
             }
         }
@@ -217,9 +217,9 @@ pub trait AltIntegerImpl: AltrepImpl {
         }
     }
 
-    /// Return TRUE if this vector is sorted, FALSE if not and NA_LOGICAL if unknown.
-    fn is_sorted(&self) -> Bool {
-        NA_LOGICAL
+    /// Return TRUE if this vector is sorted, FALSE if not and Rbool::na() if unknown.
+    fn is_sorted(&self) -> Rbool {
+        Rbool::na()
     }
 
     /// Return true if this vector does not contain NAs.
@@ -271,9 +271,9 @@ pub trait AltRealImpl: AltrepImpl {
         for i in 0..len {
             let val = self.elt(i);
             if !val.is_na() {
-                tot += val.0;
-                min = min.min(val.0);
-                max = max.max(val.0);
+                tot += val.inner();
+                min = min.min(val.inner());
+                max = max.max(val.inner());
                 nas += 1;
             }
         }
@@ -298,9 +298,9 @@ pub trait AltRealImpl: AltrepImpl {
         }
     }
 
-    /// Return TRUE if this vector is sorted, FALSE if not and NA_LOGICAL if unknown.
-    fn is_sorted(&self) -> Bool {
-        NA_LOGICAL
+    /// Return TRUE if this vector is sorted, FALSE if not and Rbool::na() if unknown.
+    fn is_sorted(&self) -> Rbool {
+        Rbool::na()
     }
 
     /// Return true if this vector does not contain NAs.
@@ -350,7 +350,7 @@ pub trait AltLogicalImpl: AltrepImpl {
         for i in 0..len {
             let val = self.elt(i);
             if !val.is_na() {
-                tot += val.0 as i64;
+                tot += val.inner() as i64;
                 nas += 1;
             }
         }
@@ -358,10 +358,10 @@ pub trait AltLogicalImpl: AltrepImpl {
     }
 
     /// Get a single element from this vector.
-    fn elt(&self, _index: usize) -> Bool;
+    fn elt(&self, _index: usize) -> Rbool;
 
     /// Get a multiple elements from this vector.
-    fn get_region(&self, index: usize, data: &mut [Bool]) -> usize {
+    fn get_region(&self, index: usize, data: &mut [Rbool]) -> usize {
         let len = self.length();
         if index > len {
             0
@@ -375,9 +375,9 @@ pub trait AltLogicalImpl: AltrepImpl {
         }
     }
 
-    /// Return TRUE if this vector is sorted, FALSE if not and NA_LOGICAL if unknown.
-    fn is_sorted(&self) -> Bool {
-        NA_LOGICAL
+    /// Return TRUE if this vector is sorted, FALSE if not and Rbool::na() if unknown.
+    fn is_sorted(&self) -> Rbool {
+        Rbool::na()
     }
 
     /// Return true if this vector does not contain NAs.
@@ -390,7 +390,7 @@ pub trait AltLogicalImpl: AltrepImpl {
     fn sum(&self, remove_nas: bool) -> Robj {
         let (tot, _min, _max, nas, len) = self.tot_min_max_nas();
         if !remove_nas && nas != 0 || remove_nas && nas == len {
-            NA_LOGICAL.into()
+            Rbool::na().into()
         } else {
             tot.into()
         }
@@ -444,9 +444,9 @@ pub trait AltStringImpl {
     /// Set a single element in this vector.
     fn set_elt(&mut self, _index: usize, _value: Rstr) {}
 
-    /// Return TRUE if this vector is sorted, FALSE if not and NA_LOGICAL if unknown.
-    fn is_sorted(&self) -> Bool {
-        NA_LOGICAL
+    /// Return TRUE if this vector is sorted, FALSE if not and Rbool::na() if unknown.
+    fn is_sorted(&self) -> Rbool {
+        Rbool::na()
     }
 
     /// Return true if this vector does not contain NAs.
@@ -701,7 +701,7 @@ impl Altrep {
                 x: SEXP,
                 i: R_xlen_t,
             ) -> c_int {
-                Altrep::get_state::<StateType>(x).elt(i as usize).0 as c_int
+                Altrep::get_state::<StateType>(x).elt(i as usize).inner() as c_int
             }
 
             unsafe extern "C" fn altinteger_Get_region<StateType: AltIntegerImpl + 'static>(
@@ -717,7 +717,7 @@ impl Altrep {
             unsafe extern "C" fn altinteger_Is_sorted<StateType: AltIntegerImpl + 'static>(
                 x: SEXP,
             ) -> c_int {
-                Altrep::get_state::<StateType>(x).is_sorted().0 as c_int
+                Altrep::get_state::<StateType>(x).is_sorted().inner() as c_int
             }
 
             unsafe extern "C" fn altinteger_No_NA<StateType: AltIntegerImpl + 'static>(
@@ -779,7 +779,7 @@ impl Altrep {
                 x: SEXP,
                 i: R_xlen_t,
             ) -> f64 {
-                Altrep::get_state::<StateType>(x).elt(i as usize).0
+                Altrep::get_state::<StateType>(x).elt(i as usize).inner()
             }
 
             unsafe extern "C" fn altreal_Get_region<StateType: AltRealImpl + 'static>(
@@ -795,7 +795,7 @@ impl Altrep {
             unsafe extern "C" fn altreal_Is_sorted<StateType: AltRealImpl + 'static>(
                 x: SEXP,
             ) -> c_int {
-                Altrep::get_state::<StateType>(x).is_sorted().0 as c_int
+                Altrep::get_state::<StateType>(x).is_sorted().inner() as c_int
             }
 
             unsafe extern "C" fn altreal_No_NA<StateType: AltRealImpl + 'static>(x: SEXP) -> c_int {
@@ -854,7 +854,7 @@ impl Altrep {
                 x: SEXP,
                 i: R_xlen_t,
             ) -> c_int {
-                Altrep::get_state::<StateType>(x).elt(i as usize).0 as c_int
+                Altrep::get_state::<StateType>(x).elt(i as usize).inner() as c_int
             }
 
             unsafe extern "C" fn altlogical_Get_region<StateType: AltLogicalImpl + 'static>(
@@ -863,14 +863,14 @@ impl Altrep {
                 n: R_xlen_t,
                 buf: *mut c_int,
             ) -> R_xlen_t {
-                let slice = std::slice::from_raw_parts_mut(buf as *mut Bool, n as usize);
+                let slice = std::slice::from_raw_parts_mut(buf as *mut Rbool, n as usize);
                 Altrep::get_state::<StateType>(x).get_region(i as usize, slice) as R_xlen_t
             }
 
             unsafe extern "C" fn altlogical_Is_sorted<StateType: AltLogicalImpl + 'static>(
                 x: SEXP,
             ) -> c_int {
-                Altrep::get_state::<StateType>(x).is_sorted().0 as c_int
+                Altrep::get_state::<StateType>(x).is_sorted().inner() as c_int
             }
 
             unsafe extern "C" fn altlogical_No_NA<StateType: AltLogicalImpl + 'static>(
@@ -1001,7 +1001,7 @@ impl Altrep {
             unsafe extern "C" fn altstring_Is_sorted<StateType: AltStringImpl + 'static>(
                 x: SEXP,
             ) -> c_int {
-                Altrep::get_state::<StateType>(x).is_sorted().0 as c_int
+                Altrep::get_state::<StateType>(x).is_sorted().inner() as c_int
             }
 
             unsafe extern "C" fn altstring_No_NA<StateType: AltStringImpl + 'static>(
@@ -1030,6 +1030,13 @@ impl Altrep {
         Rint,
         i32
     );
+    make_from_iterator!(
+        make_altlogical_from_iterator,
+        make_altlogical_class,
+        AltLogicalImpl,
+        Rbool,
+        i32
+    );
     make_from_iterator!(
         make_altreal_from_iterator,
         make_altreal_class,
diff --git a/extendr-api/src/wrapper/doubles.rs b/extendr-api/src/wrapper/doubles.rs
index 4be3a4264..e9440293b 100644
--- a/extendr-api/src/wrapper/doubles.rs
+++ b/extendr-api/src/wrapper/doubles.rs
@@ -9,10 +9,10 @@ use std::iter::FromIterator;
 /// use extendr_api::prelude::*;
 /// test! {
 ///     let mut vec = (0..5).map(|i| (i as f64).into()).collect::<Doubles>();
-///     vec.iter_mut().for_each(|v| *v = *v + 10f64);
-///     assert_eq!(vec.elt(0), 10f64);
+///     vec.iter_mut().for_each(|v| *v = *v + 10.0);
+///     assert_eq!(vec.elt(0), 10.0);
 ///     let sum = vec.iter().sum::<Rfloat>();
-///     assert_eq!(sum, 60f64);
+///     assert_eq!(sum, 60.0);
 /// }
 /// ```  
 #[derive(Debug, PartialEq, Clone)]
@@ -21,12 +21,13 @@ pub struct Doubles {
 }
 
 crate::wrapper::macros::gen_vector_wrapper_impl!(
-    Doubles, // Implements for
-    Rfloat,  // Element type
-    f64,     // Raw element type
-    REAL,    // `R` functions prefix
-    REALSXP, // `SEXP`
-    double   // Singular type name used in docs
+    vector_type: Doubles,
+    scalar_type: Rfloat,
+    primitive_type: f64,
+    r_prefix: REAL,
+    SEXP: REALSXP,
+    doc_name: double,
+    altrep_constructor: make_altreal_from_iterator,
 );
 
 #[cfg(test)]
@@ -37,23 +38,23 @@ mod tests {
     fn from_iterator() {
         test! {
             let vec : Doubles = (0..3).map(|i| (i as f64).into()).collect();
-            assert_eq!(vec, Doubles::from_values([0f64, 1f64, 2f64]));
+            assert_eq!(vec, Doubles::from_values([0.0, 1.0, 2.0]));
         }
     }
     #[test]
     fn iter_mut() {
         test! {
-            let mut vec = Doubles::from_values([0f64, 1f64, 2f64, 3f64]);
-            vec.iter_mut().for_each(|v| *v = *v + 1f64);
-            assert_eq!(vec, Doubles::from_values([1f64, 2f64, 3f64, 4f64]));
+            let mut vec = Doubles::from_values([0.0, 1.0, 2.0, 3.0]);
+            vec.iter_mut().for_each(|v| *v = *v + 1.0);
+            assert_eq!(vec, Doubles::from_values([1.0, 2.0, 3.0, 4.0]));
         }
     }
 
     #[test]
     fn iter() {
         test! {
-            let vec = Doubles::from_values(0..3);
-            assert_eq!(vec.iter().sum::<Rfloat>(), 3f64);
+            let vec = Doubles::from_values([0.0, 1.0, 2.0, 3.0]);
+            assert_eq!(vec.iter().sum::<Rfloat>(), 6.0);
         }
     }
 
@@ -61,13 +62,13 @@ mod tests {
     fn from_values_short() {
         test! {
             // Short (<64k) vectors are allocated.
-            let vec = Doubles::from_values((0..3).map(|i| 2f64 - i as f64));
+            let vec = Doubles::from_values((0..3).map(|i| 2.0 - i as f64));
             assert_eq!(vec.is_altrep(), false);
-            assert_eq!(r!(vec.clone()), r!([2f64, 1f64, 0f64]));
-            assert_eq!(vec.elt(1), 1f64);
-            let mut dest = [0f64; 2];
+            assert_eq!(r!(vec.clone()), r!([2.0, 1.0, 0.0]));
+            assert_eq!(vec.elt(1), 1.0);
+            let mut dest = [0.0.into(); 2];
             vec.get_region(1, &mut dest);
-            assert_eq!(dest, [1f64, 0f64]);
+            assert_eq!(dest, [1.0, 0.0]);
         }
     }
     #[test]
@@ -76,10 +77,10 @@ mod tests {
             // Long (>=64k) vectors are lazy ALTREP objects.
             let vec = Doubles::from_values((0..1000000000).map(|x| x as f64));
             assert_eq!(vec.is_altrep(), true);
-            assert_eq!(vec.elt(12345678), 12345678f64);
-            let mut dest = [0f64; 2];
+            assert_eq!(vec.elt(12345678), 12345678.0);
+            let mut dest = [0.0.into(); 2];
             vec.get_region(12345678, &mut dest);
-            assert_eq!(dest, [12345678f64, 12345679f64]);
+            assert_eq!(dest, [12345678.0, 12345679.0]);
         }
     }
 
diff --git a/extendr-api/src/wrapper/integers.rs b/extendr-api/src/wrapper/integers.rs
index 36386a46a..b86080ebf 100644
--- a/extendr-api/src/wrapper/integers.rs
+++ b/extendr-api/src/wrapper/integers.rs
@@ -21,12 +21,13 @@ pub struct Integers {
 }
 
 crate::wrapper::macros::gen_vector_wrapper_impl!(
-    Integers, // Implements for
-    Rint,     // Element type
-    i32,      // Raw element type
-    INTEGER,  // `R` functions prefix
-    INTSXP,   // `SEXP`
-    integer   // Singular type name used in docs
+    vector_type: Integers, // Implements for
+    scalar_type: Rint,     // Element type
+    primitive_type: i32,   // Raw element type
+    r_prefix: INTEGER,     // `R` functions prefix
+    SEXP: INTSXP,          // `SEXP`
+    doc_name: integer,     // Singular type name used in docs
+    altrep_constructor: make_altinteger_from_iterator,
 );
 
 #[cfg(test)]
@@ -66,7 +67,7 @@ mod tests {
             assert_eq!(vec.is_altrep(), false);
             assert_eq!(r!(vec.clone()), r!([2, 1, 0]));
             assert_eq!(vec.elt(1), 1);
-            let mut dest = [0; 2];
+            let mut dest = [0.into(); 2];
             vec.get_region(1, &mut dest);
             assert_eq!(dest, [1, 0]);
         }
@@ -79,7 +80,7 @@ mod tests {
             let vec = Integers::from_values(0..1000000000);
             assert_eq!(vec.is_altrep(), true);
             assert_eq!(vec.elt(12345678), 12345678);
-            let mut dest = [0; 2];
+            let mut dest = [0.into(); 2];
             vec.get_region(12345678, &mut dest);
             assert_eq!(dest, [12345678, 12345679]);
         }
diff --git a/extendr-api/src/wrapper/logicals.rs b/extendr-api/src/wrapper/logicals.rs
new file mode 100644
index 000000000..5d4c92c6f
--- /dev/null
+++ b/extendr-api/src/wrapper/logicals.rs
@@ -0,0 +1,129 @@
+use super::scalar::Rbool;
+use super::*;
+use std::iter::FromIterator;
+
+/// An obscure `NA`-aware wrapper for R's logical vectors.
+/// Can be used to iterate over vectors obtained from R
+/// or to create new vectors that can be returned back to R.
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///     // Collect builds a Logicals from an iterator
+///     let mut vec = (0..5).map(|i| (i % 2 == 0).into()).collect::<Logicals>();
+///     // elt accesses a single element (altrep aware).
+///     assert_eq!(vec.elt(0), true);
+///     // Logicals behaves like &[Rbool]
+///     assert_eq!(vec[1], false);
+/// }
+/// ```  
+#[derive(Debug, PartialEq, Clone)]
+pub struct Logicals {
+    pub(crate) robj: Robj,
+}
+
+crate::wrapper::macros::gen_vector_wrapper_impl!(
+    vector_type: Logicals, // Implements for
+    scalar_type: Rbool,    // Element type
+    primitive_type: i32,   // Raw element type
+    r_prefix: LOGICAL,     // `R` functions prefix
+    SEXP: LGLSXP,          // `SEXP`
+    doc_name: logical,     // Singular type name used in docs
+    altrep_constructor: make_altlogical_from_iterator,
+);
+
+#[cfg(test)]
+mod tests {
+    use crate::prelude::*;
+
+    #[test]
+    fn from_iterator() {
+        test! {
+            let vec : Logicals = (0..3).map(|i| (i % 2 == 0).into()).collect();
+            assert_eq!(vec, Logicals::from_values([true, false, true]));
+        }
+    }
+
+    #[test]
+    fn iter_mut() {
+        test! {
+            let mut vec = Logicals::from_values([true, false, true]);
+            vec.iter_mut().for_each(|v| *v = true.into());
+            assert_eq!(vec, Logicals::from_values([true, true, true]));
+        }
+    }
+
+    // #[test]
+    // fn iter() {
+    //     test! {
+    //         let mut vec = Logicals::from_values([true, false, true]);
+    //         assert_eq!(vec.iter().sum::<Rint>(), 3);
+    //     }
+    // }
+
+    #[test]
+    fn from_values_short() {
+        test! {
+            // Short (<64k) vectors are allocated.
+            let vec = Logicals::from_values([true, false, true]);
+            assert_eq!(vec.is_altrep(), false);
+            assert_eq!(r!(vec.clone()), r!([true, false, true]));
+            assert_eq!(vec.elt(1), false);
+            let mut dest = [false.into(); 2];
+            vec.get_region(1, &mut dest);
+            assert_eq!(dest, [false, true]);
+        }
+    }
+
+    #[test]
+    fn from_values_long() {
+        test! {
+            // Long (>=64k) vectors a lazy ALTREP objects.
+            let vec = Logicals::from_values((0..1000000000).map(|_| Rbool::from(true)));
+            assert_eq!(vec.is_altrep(), true);
+            assert_eq!(vec.elt(12345678), true);
+            let mut dest = [false.into(); 2];
+            vec.get_region(12345678, &mut dest);
+            assert_eq!(dest, [true, true]);
+        }
+    }
+
+    #[test]
+    fn new() {
+        test! {
+            let vec = Logicals::new(10);
+            assert_eq!(vec.is_logical(), true);
+            assert_eq!(vec.len(), 10);
+        }
+    }
+}
+
+// TODO: this should be a trait.
+impl Logicals {
+    pub fn set_elt(&mut self, index: usize, val: Rbool) {
+        unsafe {
+            SET_INTEGER_ELT(self.get(), index as R_xlen_t, val.inner());
+        }
+    }
+}
+
+impl Deref for Logicals {
+    type Target = [Rbool];
+
+    /// Treat Logicals as if it is a slice, like Vec<Rint>
+    fn deref(&self) -> &Self::Target {
+        unsafe {
+            let ptr = DATAPTR_RO(self.get()) as *const Rbool;
+            std::slice::from_raw_parts(ptr, self.len())
+        }
+    }
+}
+
+impl DerefMut for Logicals {
+    /// Treat Logicals 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 Rbool;
+            std::slice::from_raw_parts_mut(ptr, self.len())
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/macros.rs b/extendr-api/src/wrapper/macros.rs
index c567c890f..1a9cafef7 100644
--- a/extendr-api/src/wrapper/macros.rs
+++ b/extendr-api/src/wrapper/macros.rs
@@ -1,18 +1,13 @@
 /// Generates `impl` block and required traits for a vector type.
 macro_rules! gen_vector_wrapper_impl {
     (
-        /// Vector type for which traits are implemented.
-        $type : ident,
-        /// `NA`-aware element type.
-        $type_elem : ty,
-        /// Underlying primitive type.
-        $type_prim : ty,
-        /// `R` type name.
-        $r_type : ident,
-        /// `R` `SEXP`.
-        $sexp : ident,
-        /// Singular name of the type, used in doc strings.
-        $doc_name : ident
+        vector_type: $type : ident,
+        scalar_type: $scalar_type : ty,
+        primitive_type: $primitive_type : ty,
+        r_prefix: $r_prefix : ident,
+        SEXP: $sexp : ident,
+        doc_name: $doc_name : ident,
+        altrep_constructor: $altrep_constructor : ident,
     ) => {
 
         // Under this size, vectors are manifest.
@@ -32,13 +27,13 @@ macro_rules! gen_vector_wrapper_impl {
                 #[doc = "use extendr_api::prelude::*;"]
                 #[doc = "test! {"]
                 #[doc = "   let vec = " $type "::new(10);"]
-                #[doc = "   assert_eq!(vec.is_" $r_type:lower "(), true);"]
+                #[doc = "   assert_eq!(vec.is_" $r_prefix:lower "(), true);"]
                 #[doc = "   assert_eq!(vec.len(), 10);"]
                 #[doc = "}"]
                 #[doc = "```"]
                 pub fn new(len: usize) -> $type {
                     // TODO: Check if impacts performance.
-                    let iter = (0..len).map(|_| <$type_prim>::default());
+                    let iter = (0..len).map(|_| <$primitive_type>::default());
                     <$type>::from_values(iter)
                 }
             }
@@ -50,18 +45,18 @@ macro_rules! gen_vector_wrapper_impl {
                 where
                     V: IntoIterator,
                     V::IntoIter: ExactSizeIterator + std::fmt::Debug + Clone + 'static + std::any::Any,
-                    V::Item: Into<$type_prim>,
+                    V::Item: Into<$scalar_type>,
                 {
                     single_threaded(|| {
                         let values: V::IntoIter = values.into_iter();
 
                         let robj = if values.len() >= SHORT_VECTOR_LENGTH {
-                            Altrep::[<make_alt $r_type:lower _from_iterator>](values)
+                            Altrep::$altrep_constructor(values)
                                 .try_into()
                                 .unwrap()
                         } else {
                             let mut robj = Robj::alloc_vector($sexp, values.len());
-                            let dest: &mut [$type_prim] = robj.as_typed_slice_mut().unwrap();
+                            let dest: &mut [$scalar_type] = robj.as_typed_slice_mut().unwrap();
 
                             for (d, v) in dest.iter_mut().zip(values) {
                                 *d = v.into();
@@ -80,44 +75,44 @@ macro_rules! gen_vector_wrapper_impl {
                 #[doc = "use extendr_api::prelude::*;"]
                 #[doc = "test! {"]
                 #[doc = "   let vec = " $type "::new(10);"]
-                #[doc = "   assert_eq!(vec.elt(0), <"$type_elem">::default());"]
-                #[doc = "   assert_eq!(vec.elt(9), <"$type_elem">::default());"]
+                #[doc = "   assert_eq!(vec.elt(0), <"$scalar_type">::default());"]
+                #[doc = "   assert_eq!(vec.elt(9), <"$scalar_type">::default());"]
                 #[doc = "   assert!(vec.elt(10).is_na());"]
                 #[doc = "}"]
                 #[doc = "```"]
-                pub fn elt(&self, index: usize) -> $type_elem {
+                pub fn elt(&self, index: usize) -> $scalar_type {
                     // Defensive check for oob
                     // This check will not be needed in later releases of R
                     if(index >= self.len()) {
-                        <$type_elem>::na()
+                        <$scalar_type>::na()
                     } else {
-                        unsafe { [<$r_type _ELT>](self.get(), index as R_xlen_t).into() }
+                        unsafe { [<$r_prefix _ELT>](self.get(), index as R_xlen_t).into() }
                     }
                 }
             }
 
             /// Get a region of elements from the vector.
-            pub fn get_region(&self, index: usize, dest: &mut [$type_prim]) -> usize {
+            pub fn get_region(&self, index: usize, dest: &mut [$scalar_type]) -> usize {
                 unsafe {
-                    let ptr = dest.as_mut_ptr();
-                    paste::paste!{ [<$r_type _GET_REGION>](self.get(), index as R_xlen_t, dest.len() as R_xlen_t, ptr) as usize }
+                    let ptr : *mut $primitive_type = dest.as_mut_ptr() as *mut $primitive_type;
+                    paste::paste!{ [<$r_prefix _GET_REGION>](self.get(), index as R_xlen_t, dest.len() as R_xlen_t, ptr) as usize }
                 }
             }
 
             /// Return `TRUE` if the vector is sorted, `FALSE` if not, or `NA_BOOL` if unknown.
-            pub fn is_sorted(&self) -> Bool {
-                unsafe { paste::paste!{ [<$r_type _IS_SORTED>](self.get()).into() } }
+            pub fn is_sorted(&self) -> Rbool {
+                unsafe { paste::paste!{ [<$r_prefix _IS_SORTED>](self.get()).into() } }
             }
 
             /// Return `TRUE` if the vector has no `NA`s, `FALSE` if any, or `NA_BOOL` if unknown.
-            pub fn no_na(&self) -> Bool {
-                unsafe { paste::paste!{ [<$r_type _NO_NA>](self.get()).into() } }
+            pub fn no_na(&self) -> Rbool {
+                unsafe { paste::paste!{ [<$r_prefix _NO_NA>](self.get()).into() } }
             }
 
             paste::paste!{
                 #[doc = "Return an iterator for a " $doc_name " object."]
                 #[doc = "Forces ALTREP objects to manifest."]
-                pub fn iter(&self) -> impl Iterator<Item = $type_elem> {
+                pub fn iter(&self) -> impl Iterator<Item = $scalar_type> {
                     self.as_robj().as_typed_slice().unwrap().iter().cloned()
                 }
             }
@@ -125,22 +120,22 @@ macro_rules! gen_vector_wrapper_impl {
             paste::paste!{
                 #[doc = "Return a writable iterator for a " $doc_name " object."]
                 #[doc = "Forces ALTREP objects to manifest."]
-                pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut $type_elem> {
+                pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut $scalar_type> {
                     self.as_robj_mut().as_typed_slice_mut().unwrap().iter_mut()
                 }
             }
         }
 
-        impl FromIterator<$type_elem> for $type {
+        impl FromIterator<$scalar_type> for $type {
             /// A more generalised iterator collector for small vectors.
             /// Generates a non-ALTREP vector.
-            fn from_iter<T: IntoIterator<Item = $type_elem>>(iter: T) -> Self {
+            fn from_iter<T: IntoIterator<Item = $scalar_type>>(iter: T) -> Self {
                 // Collect into a vector first.
                 // TODO: specialise for ExactSizeIterator.
-                let values: Vec<$type_elem> = iter.into_iter().collect();
+                let values: Vec<$scalar_type> = iter.into_iter().collect();
 
                 let mut robj = Robj::alloc_vector($sexp, values.len());
-                let dest: &mut [$type_elem] = robj.as_typed_slice_mut().unwrap();
+                let dest: &mut [$scalar_type] = robj.as_typed_slice_mut().unwrap();
 
                 for (d, v) in dest.iter_mut().zip(values) {
                     *d = v;
diff --git a/extendr-api/src/wrapper/mod.rs b/extendr-api/src/wrapper/mod.rs
index 9e3498b55..f4ec8bd60 100644
--- a/extendr-api/src/wrapper/mod.rs
+++ b/extendr-api/src/wrapper/mod.rs
@@ -14,6 +14,7 @@ pub mod function;
 pub mod integers;
 pub mod lang;
 pub mod list;
+pub mod logicals;
 mod macros;
 pub mod matrix;
 pub mod nullable;
@@ -39,6 +40,7 @@ pub use function::Function;
 pub use integers::Integers;
 pub use lang::Language;
 pub use list::{FromList, List, ListIter};
+pub use logicals::Logicals;
 pub use matrix::{MatrixConversions, RArray, RColumn, RMatrix, RMatrix3D};
 pub use nullable::Nullable;
 pub use pairlist::{Pairlist, PairlistIter};
@@ -193,6 +195,7 @@ make_conversions!(Altrep, ExpectedAltrep, is_altrep, "Not an Altrep type");
 make_conversions!(S4, ExpectedS4, is_s4, "Not a S4 type");
 
 make_conversions!(Integers, ExpectedInteger, is_integer, "Not an integer type");
+make_conversions!(Logicals, ExpectedLogical, is_logical, "Not a logical type");
 make_conversions!(Doubles, ExpectedReal, is_real, "Not a floating point type");
 // make_conversions!(Function, ExpectedFunction, is_function, "Not a function");
 
diff --git a/extendr-api/src/wrapper/strings.rs b/extendr-api/src/wrapper/strings.rs
index c6f18cad2..5842e4c74 100644
--- a/extendr-api/src/wrapper/strings.rs
+++ b/extendr-api/src/wrapper/strings.rs
@@ -93,12 +93,12 @@ impl Strings {
     }
 
     /// Return `TRUE` if the vector is sorted, `FALSE` if not, or `NA_BOOL` if unknown.
-    pub fn is_sorted(&self) -> Bool {
+    pub fn is_sorted(&self) -> Rbool {
         unsafe { STRING_IS_SORTED(self.get()).into() }
     }
 
     /// Return `TRUE` if the vector has no `NA`s, `FALSE` if any, or `NA_BOOL` if unknown.
-    pub fn no_na(&self) -> Bool {
+    pub fn no_na(&self) -> Rbool {
         unsafe { STRING_NO_NA(self.get()).into() }
     }
 }


Test patch:
diff --git a/extendr-api/src/robj/tests.rs b/extendr-api/src/robj/tests.rs
index e02c8b563..4c7aaacfe 100644
--- a/extendr-api/src/robj/tests.rs
+++ b/extendr-api/src/robj/tests.rs
@@ -5,8 +5,8 @@ fn test_debug() {
     test! {
         // Special values
         assert_eq!(format!("{:?}", r!(NULL)), "r!(NULL)");
-        assert_eq!(format!("{:?}", r!(TRUE)), "r!(TRUE)");
-        assert_eq!(format!("{:?}", r!(FALSE)), "r!(FALSE)");
+        assert_eq!(format!("{:?}", r!(TRUE)), "r!(true)");
+        assert_eq!(format!("{:?}", r!(FALSE)), "r!(false)");
 
         // Scalars
         assert_eq!(format!("{:?}", r!(1)), "r!(1)");
@@ -31,7 +31,7 @@ fn test_debug() {
         // Logical
         assert_eq!(
             format!("{:?}", r!([TRUE, FALSE, NA_LOGICAL])),
-            "r!([TRUE, FALSE, NA_LOGICAL])"
+            "r!([true, false, na])"
         );
     }
 }
@@ -153,7 +153,7 @@ fn test_try_from_robj() {
 
         assert_eq!(<Vec::<i32>>::try_from(Robj::from(1)), Ok(vec![1]));
         assert_eq!(<Vec::<f64>>::try_from(Robj::from(1.)), Ok(vec![1.]));
-        assert_eq!(<Vec::<Bool>>::try_from(Robj::from(TRUE)), Ok(vec![TRUE]));
+        assert_eq!(<Vec::<Rbool>>::try_from(Robj::from(TRUE)), Ok(vec![TRUE]));
         assert_eq!(<Vec::<u8>>::try_from(Robj::from(0_u8)), Ok(vec![0_u8]));
 
         // TODO: once related todos resolved in try_from_robj.rs, add tests for
@@ -170,18 +170,18 @@ fn test_try_from_robj() {
         assert!(<Integers>::try_from(r!([true])).is_err());
 
         // TODO: reinstate.
-        // assert_eq!(<Logicals>::try_from(r!([true, false])).unwrap().collect::<Vec<Bool>>(), vec![TRUE, FALSE]);
+        // assert_eq!(<Logicals>::try_from(r!([true, false])).unwrap().collect::<Vec<Rbool>>(), vec![TRUE, FALSE]);
         // assert!(<Logicals>::try_from(r!([1])).is_err());
 
         assert_eq!(<&[i32]>::try_from(Robj::from(1)), Ok(&[1][..]));
         assert_eq!(<&[f64]>::try_from(Robj::from(1.)), Ok(&[1.][..]));
-        assert_eq!(<&[Bool]>::try_from(Robj::from(TRUE)), Ok(&[TRUE][..]));
+        assert_eq!(<&[Rbool]>::try_from(Robj::from(TRUE)), Ok(&[TRUE][..]));
         assert_eq!(<&[u8]>::try_from(Robj::from(0_u8)), Ok(&[0_u8][..]));
 
         // Note the Vec<> cases use the same logic as the slices.
         assert_eq!(<&[i32]>::try_from(Robj::from(1.0)), Err(Error::ExpectedInteger(r!(1.0))));
         assert_eq!(<&[f64]>::try_from(Robj::from(1)), Err(Error::ExpectedReal(r!(1))));
-        assert_eq!(<&[Bool]>::try_from(Robj::from(())), Err(Error::ExpectedLogical(r!(()))));
+        assert_eq!(<&[Rbool]>::try_from(Robj::from(())), Err(Error::ExpectedLogical(r!(()))));
         assert_eq!(<&[u8]>::try_from(Robj::from(())), Err(Error::ExpectedRaw(r!(()))));
 
         let hello = Robj::from("hello");
@@ -255,7 +255,7 @@ fn test_try_from_robj() {
 #[test]
 fn test_to_robj() {
     test! {
-        assert_eq!(Robj::from(true), Robj::from([Bool::from(true)]));
+        assert_eq!(Robj::from(true), Robj::from([Rbool::from(true)]));
         //assert_eq!(Robj::from(1_u8), Robj::from(1));
         assert_eq!(Robj::from(1_u16), Robj::from(1));
         assert_eq!(Robj::from(1_u32), Robj::from(1.));
@@ -388,7 +388,7 @@ fn input_iterator_test() {
         assert_eq!(iter.collect::<Vec<_>>(), src);
 
         /*
-        let src: &[Bool] = &[TRUE, FALSE, TRUE];
+        let src: &[Rbool] = &[TRUE, FALSE, TRUE];
         let robj = Robj::from(src);
         let iter = <Logical>::from_robj(&robj).unwrap();
         assert_eq!(iter.collect::<Vec<_>>(), src);
diff --git a/extendr-api/tests/altrep_tests.rs b/extendr-api/tests/altrep_tests.rs
index f7f9a862e..ea909fc31 100644
--- a/extendr-api/tests/altrep_tests.rs
+++ b/extendr-api/tests/altrep_tests.rs
@@ -47,9 +47,9 @@ fn test_altinteger() {
         assert!(integers_w_missing.elt(5).is_na());
 
         // tests for get_region()
-        let mut dest = [0i32; 2];
+        let mut dest = [0.into(); 2];
         integers_w_missing.get_region(2, &mut dest);
-        assert_eq!(dest, [2i32, 3]);
+        assert_eq!(dest, [2, 3]);
     }
 }
 
@@ -105,9 +105,9 @@ fn test_altreal() {
         }
 
         // tests for get_region()
-        let mut dest = [0f64; 2];
+        let mut dest = [0.0.into(); 2];
         doubles_w_missing.get_region(2, &mut dest);
-        assert_eq!(dest, [2f64, 3f64]);
+        assert_eq!(dest, [2.0, 3.0]);
     }
 }
 
@@ -126,7 +126,7 @@ fn test_altlogical() {
         }
 
         impl AltLogicalImpl for IsEven {
-            fn elt(&self, index: usize) -> Bool {
+            fn elt(&self, index: usize) -> Rbool {
                 (index % 2 == 1).into()
             }
         }
diff --git a/extendr-api/tests/vector_tests.rs b/extendr-api/tests/vector_tests.rs
index e35734217..292c8e0c1 100644
--- a/extendr-api/tests/vector_tests.rs
+++ b/extendr-api/tests/vector_tests.rs
@@ -87,7 +87,7 @@ fn test_doubles() {
         assert_eq!(s.len(), 10);
         assert_eq!(s.rtype(), RType::Real);
 
-        let mut s = Doubles::from_values([1, 2, 3]);
+        let mut s = Doubles::from_values([1.0, 2.0, 3.0]);
         assert_eq!(s.len(), 3);
         assert_eq!(s.rtype(), RType::Real);
         assert_eq!(s.elt(0), 1.0);
diff --git a/tests/extendrtests/R/extendr-wrappers.R b/tests/extendrtests/R/extendr-wrappers.R
index c81e94e72..df938957b 100644
--- a/tests/extendrtests/R/extendr-wrappers.R
+++ b/tests/extendrtests/R/extendr-wrappers.R
@@ -30,10 +30,14 @@ get_doubles_element <- function(x, i) .Call(wrap__get_doubles_element, x, i)
 
 get_integers_element <- function(x, i) .Call(wrap__get_integers_element, x, i)
 
+get_logicals_element <- function(x, i) .Call(wrap__get_logicals_element, x, i)
+
 doubles_square <- function(input) .Call(wrap__doubles_square, input)
 
 integers_square <- function(input) .Call(wrap__integers_square, input)
 
+logicals_not <- function(input) .Call(wrap__logicals_not, input)
+
 check_default <- function(x = NULL) .Call(wrap__check_default, x)
 
 try_rfloat_na <- function() .Call(wrap__try_rfloat_na)
diff --git a/tests/extendrtests/src/rust/src/lib.rs b/tests/extendrtests/src/rust/src/lib.rs
index 8ffb26e6a..7475b3c7a 100644
--- a/tests/extendrtests/src/rust/src/lib.rs
+++ b/tests/extendrtests/src/rust/src/lib.rs
@@ -107,6 +107,11 @@ fn get_integers_element(x: Integers, i: i32) -> Rint {
     x.elt(i as usize)
 }
 
+#[extendr(use_try_from = true)]
+fn get_logicals_element(x: Logicals, i: i32) -> Rbool {
+    x.elt(i as usize)
+}
+
 #[extendr(use_try_from = true)]
 fn doubles_square(input: Doubles) -> Doubles {
     let mut result = Doubles::new(input.len());
@@ -130,12 +135,23 @@ fn integers_square(input: Integers) -> Integers {
 }
 
 #[extendr(use_try_from = true)]
-fn check_default(#[default="NULL"] x: Robj) -> bool {
-    x.is_null()
+fn logicals_not(input: Logicals) -> Logicals {
+    let mut result = Logicals::new(input.len());
+
+    for (x, y) in result.iter_mut().zip(input.iter()) {
+        *x = !y;
+    }
+
+    result
 }
 
 // Parsing
 
+#[extendr(use_try_from = true)]
+fn check_default(#[default="NULL"] x: Robj) -> bool {
+    x.is_null()
+}
+
 // Weird behavior of parameter descriptions:
 // first passes tests as is, second -- only in backqutoes.
 /// Test whether `_arg` parameters are treated correctly in R
@@ -246,9 +262,11 @@ extendr_module! {
 
     fn get_doubles_element;
     fn get_integers_element;
+    fn get_logicals_element;
 
     fn doubles_square;
     fn integers_square;
+    fn logicals_not;
 
     fn check_default;
 
diff --git a/tests/extendrtests/tests/testthat/test-vector-wrappers.R b/tests/extendrtests/tests/testthat/test-vector-wrappers.R
index db50842fa..a82df929c 100644
--- a/tests/extendrtests/tests/testthat/test-vector-wrappers.R
+++ b/tests/extendrtests/tests/testthat/test-vector-wrappers.R
@@ -9,13 +9,23 @@ test_that("Access elements of double vector", {
 })
 
 test_that("Access elements of integer vector", {
-    x <- c(42L, NA_integer_, 100L)
-    expect_equal(get_integers_element(x, 0), 42L)
-    expect_equal(get_integers_element(x, 2), 100L)
-    # Retrieving NA
-    expect_true(is.na(get_integers_element(x, 1)))
-    # OOB returns NA
-    expect_true(is.na(get_integers_element(x, 3)))
+  x <- c(42L, NA_integer_, 100L)
+  expect_equal(get_integers_element(x, 0), 42L)
+  expect_equal(get_integers_element(x, 2), 100L)
+  # Retrieving NA
+  expect_true(is.na(get_integers_element(x, 1)))
+  # OOB returns NA
+  expect_true(is.na(get_integers_element(x, 3)))
+})
+
+test_that("Access elements of logical vector", {
+  x <- c(TRUE, NA, FALSE)
+  expect_equal(get_logicals_element(x, 0), TRUE)
+  expect_equal(get_logicals_element(x, 2), FALSE)
+  # Retrieving NA
+  expect_true(is.na(get_logicals_element(x, 1)))
+  # OOB returns NA
+  expect_true(is.na(get_logicals_element(x, 3)))
 })
 
 test_that("Construct double vector from squares of given values", {
@@ -24,8 +34,13 @@ test_that("Construct double vector from squares of given values", {
 })
 
 test_that("Construct integer vector from squares of given values", {
-    x <- c(1:100)
-    expect_equal(integers_square(x), x * x)
+  x <- c(1:100)
+  expect_equal(integers_square(x), x * x)
+})
+
+test_that("Construct logical vector from negation", {
+  x <- rep(TRUE, 100)
+  expect_equal(logicals_not(x), rep(FALSE, 100))
 })
 
 test_that("Double argument type safety", {