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:

https://docs.rs/geo-geojson/0.1.0/geo_geojson/conversion/fn.from_feature.html

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).