extendr / extendr.github.io

https://extendr.github.io/
MIT License
5 stars 3 forks source link

0.7.0 migration guide #28

Closed JosiahParry closed 2 weeks ago

JosiahParry commented 2 weeks ago

We need to create a blog section and can add this in there.

## extendr 0.7.0 migration guide

The `0.7.0` version of extendr has just been released. This release has focused on cleaning up the API and ensuring that design decisions are safer and more idiomatic rust.

To this end, there are a few breaking changes that will need to be addressed. When migrating, the Rust compiler might _look_ like a red mess of error, but I can assure you, it isn't so bad! 

## Removal of `FromRobj` 

The `FromRobj` trait provided the method `from_robj()` method which allowed you to fallibly convert from one struct to another. 

This trait has been removed in favor of the standard library `TryFrom` trait. 

For example the following function definition adapted from `{rsgeo}` is no longer valid. 

```rs
fn signed_area(x: List) -> f64 {
    <&Geom>::from_robj(&xi)
        .unwrap()
        .geom
        .signed_area()
}

Instead, replace the from_robj() with try_from().

fn signed_area(x: List) -> f64 {
    <&Geom>::try_from(&xi)
        .unwrap()
        .geom
        .signed_area()
}

Removal of #[extendr(use_try_from = true)]

Because TryFrom is the default now, the macro argument use_try_from = true will cause a compiler error. For example this

#[extendr(use_try_from = true)]
fn wkb_to_geoms(x: List) -> Robj {
    x
        .into_iter()
        .map(|(_, x)| wkb_to_geom(raw_to_vecu8(x)))
        .collect::<List>()
        .into_robj()
}

becomes

#[extendr]
fn wkb_to_geoms(x: List) -> Robj {
    x
        .into_iter()
        .map(|(_, x)| wkb_to_geom(raw_to_vecu8(x)))
        .collect::<List>()
        .into_robj()
}

Setting Attributes

Prior to version 0.7.0 the Attributes trait did two undesirable things:

  1. any time you added an attribute, the result was an Robj
  2. adding an attribute did not require a mutable reference

The first was problematic because you lose the struct type when setting an attribute. The second was problematic because Attributes::set_attrib() method modified the underlying SEXP in place without requiring a mutable reference giving you an unsafe guarantee that the original SEXP would not be modified.

For example migrating from to 0.7.0 in the package {arcgisplaces} results in the compiler error:

error[E0308]: mismatched types
  --> src/lib.rs:15:13
   |
11 |   pub fn location_to_sfg(x: Option<Point>) -> Robj {
   |                                               ---- expected `extendr_api::Robj` because of return type
...
15 | /             Doubles::from_values([x, y])
16 | |                 .into_robj()
17 | |                 .set_class(&["XY", "POINT", "sfg"])
18 | |                 .unwrap()
   | |_________________________^ expected `Robj`, found `&mut Robj`

error[E0308]: mismatched types

A simplified version of the function looks like:

pub fn location_to_sfg(x: Option<Point>) -> Robj {
    let Point { x, y } = x.unwrap();
    Doubles::from_values([x, y])
        .set_class(&["XY", "POINT", "sfg"])
        .unwrap()
}

This function previously returned an Robj because that was the type returned by set_class(). This function can be rewritten as

pub fn location_to_sfg2(x: Option<Point>) -> Robj {
    let Point { x, y } = x.unwrap();
    Doubles::from_values([x, y])
        .set_class(&["XY", "POINT", "sfg"])
        .unwrap()
        .clone()
        .into_robj()
}

.set_class() returns a &mut Doubles we can clone the Doubles the result so that we have a non-mutable reference. Note that cloning only increases a reference counter and is not costly. Here .into_robj() is used to return an Robj

Alternatively, the function can now return Doubles instead if you so desire:

pub fn location_to_sfg(x: Option<Point>) -> Doubles {
    let Point { x, y } = x.unwrap();
    Doubles::from_values([x, y])
        .set_class(&["XY", "POINT", "sfg"])
        .unwrap()
        .clone()
}

R-devel Non-API changes

R-devel is currently in the process of formalizing what is and is not part of the official C-API. As a result extendr powered R packages have WARN-ings due to non-API usage.

extendr 0.7.0 hides these behind a feature flag non-api. Unfortunately, due to the 1.69 minimum supported Rust version (MSRV) of CRAN combined with the lack of an MSRV in bindgen (which is used to generate R bindings), the non-api features cannot be provided automatically and require custom generation of R bindings via libR-sys.

This will affect you if you are using the global_var(), local_var(), base_env(), various Environment, Function, Primitive, and Promise methods.

If you are affected by this, please create an issue and we can work through it together.

CGMossa commented 2 weeks ago

This is a solid post.

JosiahParry commented 2 weeks ago

@kbvernon @CGMossa let me know how we can get this into the new site.

kbvernon commented 2 weeks ago

thanks, josiah! i'm moving some content out of drafts. once i have that done. i'll puzzle over how to setup a blog here.