Ownership and function signature question

Hi, am noticing as I learn Rust, it seems the signatures of 3rd party functions used in the implementation of my own functions have a great effect on whether my function parameters can be owned or borrowed. Here is an example: I am writing some GeoJson parsing code, and I want to use this convenience function from a 3rd party crate:

pub fn from_feature<T>(feature: Feature) -> GeometryCollection<T> where
    T: Num + NumCast + PartialOrd + Copy + Float

Basically from_feature takes a geojson:: Feature and converts it into a geo:: GeometryCollection. Because it takes ownership of the feature, that requirement seems to bleed upwards and effects my function signature. In this example I want to create a Vec of these GeometryWithBounds structs. The fn is unfinished, but it compiles:

struct GeometryWithBounds<T>
where
  T: CoordinateType,
{
  geometry: Geometry<T>,
  bounding_rect: Option<Rect<T>>,
}

fn extract_geometries_and_bounds(
  feature_collection: geojson::FeatureCollection, //*** I have to take ownership of the feature_collection
) -> Vec<GeometryWithBounds<f64>> {
  feature_collection
    .features
    .into_iter()
    .flat_map(|feature| {
      let geometry_collection = from_feature::<f64>(feature); // *** Because of this
      geometry_collection
        .into_iter()
        .map(|geometry| match &geometry {
          Geometry::Polygon(polygon) => Some(GeometryWithBounds {
            geometry,
            bounding_rect: None,
          }),
          _ => None,
        })
    })
    .filter_map(|some_geometry| some_geometry)
    .collect()
}

What if I wanted to change my function signature to take a reference to the feature_collection instead of owned?

fn extract_geometries_and_bounds_borrow(
  feature_collection: &geojson::FeatureCollection, // *** might be nice to take a reference
) -> Vec<GeometryWithBounds<f64>> {
  (&feature_collection.features)
    .iter()
    .flat_map(|feature| {
      let geometry_collection = from_feature::<f64>(feature); // *** cannot get past this api call
     ...

Similarly, what if I wanted my struct to have references instead of owned:

  geometry: &Geometry<T>,
  bounding_rect: Option<&Rect<T>>,

Is that simply not possible based on the rules of the ownership universe, or (more likely) as a beginner in Rust I just have not hit upon the right combination of syntax?

Is this common where your own function signatures are to a large degree dictated by what crates and libraries you are using inside of your functions?

Hopefully I am just confused, but would greatly appreciate any advice or if there are any articles written about when to make your fn take a reference, and when to make your fn take ownership, that would be most interesting.

In general, if you have a borrowed reference and you want a new owned value, you can clone it.

For example, in this case you could use the Iterator::cloned method:

fn extract_geometries_and_bounds_borrow(
  feature_collection: &geojson::FeatureCollection,
) -> Vec<GeometryWithBounds<f64>> {
  feature_collection.features.iter().cloned()
    .flat_map(|feature| {
      // `feature` is a new `Feature` cloned from the one we borrowed.
      let geometry_collection = from_feature::<f64>(feature);
     ...
2 Likes

Thanks @mbrubeck. I forgot to mention that I do not want to clone(). In this scenario, I just want to do some analysis of the geometries and pack some selected features into a Vec. There no reason to duplicate the data in memory. I feel like clone() would be the wrong thing to do just to appease the borrow checker. What if the author of this blog was doing my code review :slight_smile:

Ah, I see that the GeometryCollection doesn't really require a clone of the entire Feature, but only a clone of the feature.geometry?.value field. You are right that this library API is limiting because it asks for more ownership than it needs. I would suggest to the author of geojson that from_feature should take &Feature instead.

Fortunately, the library does provide alternate ways, though they are not as convenient:

use geo_geojson::conversion::from_value;

fn extract_geometries_and_bounds_borrow(
  feature_collection: &geojson::FeatureCollection,
) -> Vec<GeometryWithBounds<f64>> {
  feature_collection.features.iter()
    .flat_map(|feature| {
      let geometry_collection =
        feature.geometry.map(|g| from_value::<f64>(g.value.clone()))
          .unwrap_or_else(GeometryCollection::new);
      // ...

@mbrubeck this idea you wrote actually helps quite a lot:

this library API is limiting because it asks for more ownership than it needs.

It reflects how I am feeling about my own fn implementation too :slight_smile:

To clarify, geo_geojson is not part of georust, but it's a bridge between the types in the geojson and geo crates. I think I'll try another version without using geo_geojson crate and see if I run up against the same limitations.

This has been an interesting exercise in following the dependency inversion principle (abstractions should not depend on details).

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.