31  A print helper

TipObjective

Write a function that reads a list of external pointers and turns it back into a character vector.

When we print our list of objects, it’s ugly:

parse_shapes(c("triangle", "hexagon"))
#> [[1]]
#> <pointer: 0x600000abc120>
#>
#> [[2]]
#> <pointer: 0x600000abc480>

R can’t see inside an external pointer, so all it can show is the memory address. We’d much rather see something meaningful. To do that, we write a function that walks the list, gets each Rust value back out, and produces a character vector.

Accessing the external pointer

  • External pointers are stored in a struct called…. ExternalPtr, this is a fancy type of struct
  • It has an associated type—as seen by this “turbo-fish”
  • Rust needs to know what type is inside of the pointer so that it can give you access to the methods in it
  • Each type in Rust has a special method called try_from() that fallibly tries to convert. If it can’t, we get an error!
  • extendr provides this ExternalPtr::try_from() so that we can try and get the pointer!

Here’s how this works—since we refer to the type using Self this will work for any type with #[extendr] on it.

impl RShape {
    fn get_extptr(robj: Robj) -> extendr_api::Result<ExternalPtr<Self>> {
        ExternalPtr::<Self>::try_from(robj)
    }
}

Iterating a list

  • When we iterate over a List, each item is a tuple
  • It is a fixed sized thing where each element is unnamed and can have a different type
  • The first element (accessed via .0) is the name as a &str, and the second element (via .1) is the value as an Robj

We usually ignore the name with _.

For each value we try to get the pointer back. If we succeed, we borrow the inner Rust value and produce a string from it. If not, we produce an NA.

Example

Here we turn a list of RShapes into a character vector of their vertex counts:

#[extendr]
fn format_shapes(x: List) -> Strings {
    x.into_iter()
        .map(|(_, robj)| match RShape::get_extptr(robj) {
            Ok(v) => Rstr::from(format!("{:?}", v.0)),
            Err(_) => Rstr::na(),
        })
        .collect()
}

Exercise

Note that instead of using format!() you’ll use .to_string().

  • Write format_rurls(x: List) -> Strings
  • Iterate the list, destructuring each element as (_, vi)
  • Use RUrl::get_extptr() to get the pointer back
  • On success, turn the inner Url into a string with .to_string()
  • On failure, return Rstr::na()
View solution
#[extendr]
fn format_rurls(x: List) -> Strings {
    x.into_iter()
        .map(|(_, vi)| {
            let vv = RUrl::get_extptr(vi);
            match vv {
                Ok(rurl) => Rstr::from(rurl.0.to_string()),
                Err(_) => Rstr::na(),
            }
        })
        .collect::<Strings>()
}