26 R’s Type System
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 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.
Notice EXTPTRSXP, the external pointer. Later, when we hold onto a parsed Url in R, that’s the SEXP type we’ll be using. More on that soon.
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.
- Integers are represented by 32 bit integers
i32in Rust. - Doubles are represented by 64 bit floats
f64in 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:
RfloatRintRstrRbool
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.
f64ori32we can use the.0()method.
Wrapper types
To work with vectors, we work with their wrapper types. These are:
DoublesIntegersLogicalsStrings
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.
A quick taste
A vectorized extendr function takes a wrapper type in, iterates over its scalar elements, and collects back into a wrapper type. For example, doubling every value of a numeric vector:
#[extendr]
fn double_it(x: Doubles) -> Doubles {
x.into_iter()
.map(|xi| Rfloat::from(xi.0 * 2.0))
.collect::<Doubles>()
}We’ll put this exact shape to work in the warm-up exercise below.
Exercise (3 minutes)
Write a vectorized function powi() that raises each element of a numeric vector to an integer power.
- It takes a
Doubles(x) and ani32(power) - It returns a
Doubles - Iterate over the scalar
Rfloatelements, reach the innerf64with.0, and raise it withf64::powi() - Collect into a
Doubles
View solution
#[extendr]
fn powi(x: Doubles, power: i32) -> Doubles {
x.into_iter().map(|xi| xi.0.powi(power)).collect()
}