Can't get first generic type to work


#1

I’m trying to build a library to query Spatialite for geometric features. Initially I’ve queried the database for individual feature types with methods like points_of_interest, paths, etc. But I’d like to transition to something more generic. Yesterday I iterated through all spatial tables, then composed a union database query that retrieves all geospatial features in an area regardless of what table they’re in. My code looks a bit like:

use std::any::Any;
use std::boxed::Box;

trait Feature {}

struct Point;

impl Feature for Point {}

struct Line;

impl Feature for Line{}

struct Features(Vec<Box<Feature>>);

impl Features {
    fn new(features: Vec<Box<Feature>>) -> Self {
        Features(features)
    }

    fn all(&self) -> &Vec<Box<Feature>> { &self.0 }
}

fn main() {
    let point = Point;
    let line = Line;
    let collection: Vec<Box<Feature>> = vec![
        Box::new(line),
        Box::new(point)
    ];
    let features = Features::new(collection);
}

Next I want to extract specific kinds of features. So an API might run something like:

    let points: Vec<Box<Point>> = features.extract<Point>();

But nothing I do gets this working. Here’s my current attempt:

    /*fn extract<T>(&self) -> Vec<Box<T>> where T: Feature {
        self.0.into_iter().filter(|v: &Any| v.is::<Box<T>>()).collect::<Vec<Feature>>()
    }*/

When uncommented, I get:

error[E0599]: no method named `collect` found for type `std::iter::Filter<std::vec::IntoIter<std::boxed::Box<Feature>>, [closure@src/main.rs:24:35: 24:61]>` in the current scope
  --> src/main.rs:24:63
   |
24 |         self.0.into_iter().filter(|v: &Any| v.is::<Box<T>>()).collect::<Vec<Feature>>()
   |                                                               ^^^^^^^
   |
   = note: the method `collect` exists but the following trait bounds were not satisfied:
           `std::iter::Filter<std::vec::IntoIter<std::boxed::Box<Feature>>, [closure@src/main.rs:24:35: 24:61]> : std::iter::Iterator`
           `&mut std::iter::Filter<std::vec::IntoIter<std::boxed::Box<Feature>>, [closure@src/main.rs:24:35: 24:61]> : std::iter::Iterator`

error[E0631]: type mismatch in closure arguments
  --> src/main.rs:24:28
   |
24 |         self.0.into_iter().filter(|v: &Any| v.is::<Box<T>>()).collect::<Vec<Feature>>()
   |                            ^^^^^^ -------------------------- found signature of `for<'r> fn(&'r std::any::Any + 'static) -> _`
   |                            |
   |                            expected signature of `for<'r> fn(&'r std::boxed::Box<Feature>) -> _`

error: aborting due to 2 previous errors

error: Could not compile `interfaces`.

To learn more, run the command again with --verbose.

I’ve tried a number of things to get that working, but nothing seems to. Pointers appreciated. I’m also not super attached to that particular function signature, I just want a way to filter my vec of features to extract specific ones, then get back a correctly typed Vec.

As an aside, I can’t wait for the compiler to stop inviting me to learn more quite so much, I know learning never ends,but man… :stuck_out_tongue:


#2

Here’s one way to do it:

use std::any::Any;
use std::boxed::Box;

trait Feature {}

#[derive(Debug)]
struct Point;

impl Feature for Point {}

#[derive(Debug)]
struct Line;

impl Feature for Line {}

struct Features(Vec<Box<Any>>);

impl Features {
    fn new(features: Vec<Box<Any>>) -> Self {
        Features(features)
    }

    fn extract<T>(&self) -> Vec<&T>
    where
        T: Feature + 'static,
    {
        self.0.iter().filter_map(|v| v.downcast_ref()).collect()
    }
}

fn main() {
    let point = Point;
    let line = Line;
    let collection: Vec<Box<Any>> = vec![Box::new(line), Box::new(point)];
    let features = Features::new(collection);
    println!("Points: {:?}", features.extract::<Point>());
    println!("Lines: {:?}", features.extract::<Line>());
}

Playground


#3

Hmm, does that get rid of type safety on the contained vec? I guess that
isn’t the end of the world, since the only way to create a Features
object is by an API guaranteed to return the right type. But it’d be
nice if I could preserve that somehow.

Thanks!


#4

You can expose a slightly different style of API if you want to ensure that only Feature types are put in there:

impl Features {
    fn new() -> Self {
        Features(vec![])
    }

    fn add<T: Feature + 'static>(&mut self, f: T) {
        self.0.push(Box::new(f))
    }
}

fn main() {
    let mut features = Features::new();
    features.add(Point);
    features.add(Line);
    println!("Points: {:?}", features.extract::<Point>());
    println!("Lines: {:?}", features.extract::<Line>());
}

Playground


#5

Thanks! I rewrote my example but am still hung up on what the extract
definition would be.


#6

What’s the issue with extract exactly? The playground I posted has the definition. Or did you want something different?


#7

Ah, sorry, was looking at your second post, which didn’t include an
extract method. I have this:

use std::any::Any;
use std::boxed::Box;

trait Feature {}

struct Point;

impl Feature for Point {}

struct Line;

impl Feature for Line{}

struct Features(Vec<Box<Feature>>);

impl Features {
     fn new() -> Self {
         Features(vec!())
     }

     fn add<T: Feature + 'static>(&mut self, feature: T) -> &Self {
         self.0.push(Box::new(feature));
         self
     }

     //fn all(&self) -> &Vec<Box<Feature>> { &self.0 }

     fn extract<T>(&self) -> Vec<&T> where T: Feature + 'static {
         self.0.iter().filter_map(|v| v.downcast_ref()).collect()
     }
}

fn main() {
     let point = Point;
     let line = Line;
     let mut features = Features::new();
     features.add(line);
     features.add(point);
}

And that gives:

error[E0599]: no method named `downcast_ref` found for type 
`&std::boxed::Box<Feature>` in the current scope
   --> src/main.rs:29:40

Thanks for all your help! Been banging my head against this one for
hours. :slight_smile:


#8

So my playground has the full code :slight_smile:. But in this example, you need to have a Vec<Box<Any>> internally, not Vec<Box<Feature>>. Again, the entire code is in the playground.


#9

Oh, I get it, so the Box<Vec<Any>> is required. What’s changed is that
it is only settable by the add method, ensuring that an API user can
never feed it bad data. Thanks, that makes sense. Sorry, I learn better
by typing things myself vs. just copy-pasting, and I missed that detail
when I refactored. :slight_smile:

Thanks again.


#10

No worries!

To throw it out there, this type of runtime reflection code isn’t used much in Rust; there are uses for it, but they’re s bit nichey. You might want to consider creating an enum that contains all your feature types. Or, add enough of an API to the Feature trait so you don’t need to downcast to concrete types.

Good luck and feel free to fire off questions here.


#11

Funny you should say that. I’m now working with an API that does just
that, and it’s giving me new problems. The
Geo crate defines such an enum for all
supported Geometry types, and I’m storing that in my Feature:

pub trait Feature {
     fn geometry(&self) -> Geometry<f64>;
}

I’m now trying to implement a method that returns the distance between
features using the
Distance
module, something like:

     fn distance_to(self, other: Feature) -> ...
}

But that’s not working for a whole host of reasons. Basically, Distance
is defined over all Geometry enum members, but Feature doesn’t know what
kind of Geometry it holds. So short of an exhaustive match that checks
every possible combination, I.e.:

match self.geometry() {
     Geometry::Point(p1) => match other.geometry() {
         Geometry::Point(p2) => // distance between points, maybe Haversine
         LineString(l2) => distance(...),
         // Would be great if I could do something like _(g2) => ..., 
but I understand why that'd make rustc break out in hives
         ...
     }

I’m not sure what to do.

So I’m asking this here, because it is tangentially related. I’d be
happy to switch to something like:

enum Feature {
     MapPosition(...),
     Path(...),
     PointOfInterest(...),
     Intersection(...),
     SomeCrazyFeatureForNauticalChartsInTheFuture(...),
}

It occurs to me that each feature would have a common geometry type, so
that may simplify the distance calculation logic if I can treat features
as having distinct Geometries themselves, vs. being generic things that
can have any Geometry. It doesn’t necessarily remove the complexity,
just shifts it to a different place in the graph.

Also, if it simplifies things any further, I might also allow for
distances only to make sense from MapPosition, though I’d really like
for distances to work between any features to, say, determine the
closest distance between a road Path and river Path, both LineString.

Maybe this is where I learn macros…

Thanks again.


#12

That was going to be my question - does a given Feature have a specific Geometry that it’s valid for? If so, that should reduce the # of combinations you need to support for distance calculations. It looks like the various geometries implement Distance between it and only a specific other geometry (which makes sense), so that will likely steer how you design the Feature type.


#13

OK, thanks, making some progress! Right now I have:

// Database table and ID for this feature.
#[derive(Debug)]
pub struct FeatureDetails(String, isize);

#[derive(Debug)]
enum Feature {
     MapPosition(GeoPoint<f64>),
     Path(FeatureDetails, Line<f64>),
     PointOfInterest(FeatureDetails, GeoPoint<f64>),
}

impl Feature {
...
}

My distance_to function works. It unfortunately has to exhaustively
check each feature combination, since some calculations either don’t
make sense or have custom logic.

I guess that kind of brings me back to the beginning:

#[derive(Debug)]
pub struct Features(Vec<Feature>);

impl Features {
     fn new(features: Vec<Feature>) -> Self {
         Features(features)
     }

}

Does an extract function even make sense in this context? I may, for
instance, want to extract all Feature::PointOfInterest features, then
display them to the user. I could iterate through the vector, maybe with
if-let, and imperatively build my results vector. Is that how I’d do
this? I’m rebuilding a system I’d originally created in Scala 7-8 years
ago, and there I’m used to something like:

someLargeResultSet
   .filter(() => predicateThatReturnsSmallerResultSet)
   .map(() => transformFilteredSetIntoResultType())

I guess here I’d do that in a single pass, though I kind of miss how the
Scala read almost one-for-one with my algorithm (I.e. 1-2 lines of code
corresponded with 1 sentence in my description of how I’d solve the
problem. “Start with all points. Filter out any greater than 100 meters
away. Filter out any not on the user’s path. Transform the remaining raw
rows into nicer objects with a clean interface. Return.”)

Thanks for all your help. I appreciate it.


#14

Yeah, a generic extract() function doesn’t make sense anymore. What I would probably do is allow callers to iterate over the Feature values stored in the Features struct, and then apply whatever iterator combinators they want. For common scenarios (e.g. say iterating POIs is a frequent operation), you can provide some canned convenience functions.

If you stick with the enum approach, keep in mind that each variant is still of the enum type, and not its own type. However, if you want other code to work with a concrete type then you could do something like:

enum Feature {
    PointOfInterest(PointOfInterest),
    ...
}

struct PointOfInterest {
   ...
}

Then you extract the PointOfInterest type from the PointOfInterest variant, and pass that along to code that wants a PointOfInterest type. This is somewhat similar to what you’re attempting to do with FeatureDetails, except this is used to get concrete types rather than for commoning out structure (which is what FeatureDetails is really for).