#[extendr]
fn scalar_double(x: f64) {
rprintln!("The value of x is {x}");
}
Scalar Type Mapping
This tutorial demonstrates some of the basics of passing scalar data types back and forth between Rust and R. We’ll start with simple examples using explicit Rust types but then move on to showing their extendr alternatives. Why does extendr have its own data types? For a number of reasons, of course, but the most important reason is probably that Rust types do not allow for missing values, so no NA
, NaN
, NULL
, or what have you. Fortunately, extendr types will handle missing values for you. For this reason, it is strongly recommended that you work with the extendr types whenever possible.
Scalar types
A scalar type consists of a single value, and it can only consist of a single value, whether that value is a single character string, integer, or logical. As it happens, R doesn’t have a way of representing a scalar value. That’s because everything is a vector in R, and vectors can have any arbitrary length you want. So, the closest thing to a scalar you will ever encounter in R is a vector that just so happens to have a length of one. In Rust, however, scalars are the building blocks of everything, and they come in a bewildering variety, at least for the traditional R user. Consider, for example, integers. R has just one way to represent this type of numeric value. Rust, on the other hand, has twelve!
The table below shows the most common R “scalar” types, along with their Rust and extendr equivalents.
R type | extendr type | Rust type |
---|---|---|
integer(1) |
Rint |
i32 |
double(1) |
Rfloat |
f64 |
logical(1) |
Rbool |
bool |
complex(1) |
Rcplx |
Complex<f64> |
character(1) |
Rstr |
String |
To learn more about Rust types, see section 3.2 of The Book.
Missing values
As noted above, Rust does not allow a scalar type to have a missing value, so you cannot simply pass a missing value like NA
to Rust and expect it to just work. Here is a demonstration of this issue using a simple function which adds 1.0 to x
.
#[extendr]
fn plus_one(x: f64) -> f64 {
+ 1.0
x }
You will notice that this function expects x
to be f64
, not a missing value. Passing a missing value from R to this Rust function will, therefore, result in an error.
plus_one(NA_real_)
#> Error in plus_one(NA_real_): Must not be NA.
Fortunately, the extendr types are NA
-aware, so you can, for instance, use extendr’s Rfloat
in place of f64
to handle missing values without error. Below, you will see that we have done this for the function plus_one()
.
#[extendr]
fn plus_one(x: Rfloat) -> Rfloat {
+ 1.0
x }
plus_one(NA_real_)
#> [1] NA
plus_one(4.2)
#> [1] 5.2
Additional examples
Here are some additional examples showing how to pass scalars to Rust and return them to R using Rust scalar types.
#[extendr]
fn scalar_integer(x: i32) -> i32 { x }
#[extendr]
fn scalar_logical(x: bool) -> bool { x }
scalar_integer(4L)
#> [1] 4
scalar_logical(TRUE)
#> [1] TRUE
And here are the same examples with extendr scalar types.
#[extendr]
fn scalar_integer(x: Rint) -> Rint { x }
#[extendr]
fn scalar_logical(x: Rbool) -> Rbool { x }
scalar_integer(4L)
#> [1] 4
scalar_logical(TRUE)
#> [1] TRUE
Did you notice that we didn’t give an example with character strings? Yeah, well, there’s a good reason for that. You can find out what that is by heading over to the tutorial on Character Strings.