28  R’s Type System

Objective

Get a basic grip on R’s type system and develop an intuition for how Rust types map to R types.

In R, everything is an SEXP.

All SEXP everything
All SEXP types
no SEXPTYPE Description
0 NILSXP NULL
1 SYMSXP symbols
2 LISTSXP pairlists
3 CLOSXP closures
4 ENVSXP environments
5 PROMSXP promises
6 LANGSXP language objects
7 SPECIALSXP special functions
8 BUILTINSXP builtin functions
9 CHARSXP internal character strings
10 LGLSXP logical vectors
13 INTSXP integer vectors
14 REALSXP numeric vectors
15 CPLXSXP complex vectors
16 STRSXP character vectors
17 DOTSXP dot-dot-dot object
18 ANYSXP make “any” args work
19 VECSXP list (generic vector)
20 EXPRSXP expression vector
21 BCODESXP byte code
22 EXTPTRSXP external pointer
23 WEAKREFSXP weak reference
24 RAWSXP raw vector
25 OBJSXP objects not of simple type

To extendr, an SEXP is an Robj—it can be anything.

Vector SEXP types

There are a subset of SEXP types that we actually care about—the vector kinds. For the sake of simplicity, we’ll only talk about the numeric vector types.

SEXP numeric types
  • Integers are represented by 32 bit integers i32 in Rust.
  • Doubles are represented by 64 bit floats f64 in Rust.

Scalar types

i32 and f64 do not have a concept of NA or missing. How do we address this?

extendr provides scalar types such as:

  • Rfloat
  • Rint
  • Rstr
  • Rbool

and others.

These scalar types are created using ScalarType::from(). For example:

let my_float = Rfloat::from(3.14);

Or, NAs can be created explicitly by using the associated method ScalarType::na()

let my_na = Rfloat::na();
  • Missingness can also be checked by using the my_na.is_na() method.
  • To access the inner type—e.g. f64 or i32 we can use the .inner() method.

Wrapper types

To work with vectors, we work with their wrapper types. These are:

  • Doubles
  • Integers
  • Logicals
  • Strings

We should always prefer these types over using Vec<f64> or Vec<i32>.

Cloning an extendr Robj (or other wrapper type) is quite cheap. When cloning an Robj, we are only incrementing a reference count and not performing a full copy like we do when working in R.

Wrapper types

Exercise

  • Rewrite gh_encode() to use Doubles for both the x and y argument
  • Still return Vec<String>

Solution

View hint

Use .inner() to access the f64 value from xi and yi.

View solution
#[extendr]
fn gh_encode(x: Doubles, y: Doubles, length: usize) -> Vec<String> {
    if length == 0 || length > 12 {
        throw_r_error("`length` must be between 1 and 12");
    }

    x.into_iter()
        .zip(y.into_iter())
        .map(|(xi, yi)| {
            let coord = Coord {
                x: xi.inner(),
                y: yi.inner(),
            };
            encode(coord, length).unwrap()
        })
        .collect::<Vec<_>>()
}