Carson
Carson

Reputation: 3131

Lifetime Errors using `std::convert::identity`

@cafce25 Was able to reduce my question to the following, minimal example:

This:


pub fn filter()
{
}

pub fn filter_with<F, I, O>(f: F)
where
    F: Fn(&I) -> O,
{
}

pub fn filter2()
{
    filter_with(std::convert::identity::<&()>)
}

fn id<T>(x: &T) -> &T { x }

Fails to compile, with this warning:

error: implementation of `Fn` is not general enough
  --> src/lib.rs:14:5
   |
14 |     filter_with(std::convert::identity::<&()>)
   |     ^^^^^^^^^^^ implementation of `Fn` is not general enough
   |
   = note: `fn(&'2 ()) -> &'2 () {identity::<&'2 ()>}` must implement `Fn<(&'1 (),)>`, for any lifetime `'1`...
   = note: ...but it actually implements `Fn<(&'2 (),)>`, for some specific lifetime `'2`

error: implementation of `FnOnce` is not general enough
  --> src/lib.rs:14:5
   |
14 |     filter_with(std::convert::identity::<&()>)
   |     ^^^^^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: `fn(&'2 ()) -> &'2 () {identity::<&'2 ()>}` must implement `FnOnce<(&'1 (),)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnOnce<(&'2 (),)>`, for some specific lifetime `'2`

I still do not understand the error, though. As far as I can tell, std::convert::identity definitely fits the pattern `F(&I) -> O.

My original question is preserved below:

I have a codebase with two functions:

use mongodb::bson::{self, doc, Bson};

use std::ops::{Bound, RangeBounds};

/// Creates a mongodb filter object for a given field with a given range:
///
/// `filter("foo", 0..10)` will return `{ "foo": { "$gte": 0, "$lt": 10 } }`
/// `filter("foo", 0..)` will return `{ "foo": { "$gte": 0 } }`
/// `filter("foo", ..)` will return `{}`
pub(super) fn filter<S, R, T>(field: S, range: R) -> bson::Document
where
    S: Into<String>,
    R: RangeBounds<T>,
    T: Clone + Into<Bson>,
{
    let mut filter = doc! {};

    match range.start_bound() {
        Bound::Included(t) => filter.insert("$gte", t),
        Bound::Excluded(t) => filter.insert("$gt", t),
        Bound::Unbounded => None,
    };

    match range.end_bound() {
        Bound::Included(t) => filter.insert("$lte", t),
        Bound::Excluded(t) => filter.insert("$lt", t),
        Bound::Unbounded => None,
    };

    if filter.keys().next().is_some() {
        doc! { field.into(): filter }
    } else {
        doc! {}
    }
}

The first function is a utility function that converts some RangeBounds into a mongodb-style filter.

Recently, I added this variant, to support filters that need some mapping function to be marshalled into a DB-compatible format:

/// A version of `filter` that applies a mapping function to every returned
/// value
pub(super) fn filter_with<S, R, I, F, O>(field: S, range: R, f: F) -> bson::Document
where
    S: Into<String>,
    R: RangeBounds<I>,
    O: Into<Bson>,
    F: Fn(&I) -> O,
{
    let mut filter = doc! {};

    match range.start_bound() {
        Bound::Included(t) => filter.insert("$gte", (f)(t)),
        Bound::Excluded(t) => filter.insert("$gt", (f)(t)),
        Bound::Unbounded => None,
    };

    match range.end_bound() {
        Bound::Included(t) => filter.insert("$lte", (f)(t)),
        Bound::Excluded(t) => filter.insert("$lt", (f)(t)),
        Bound::Unbounded => None,
    };

    if filter.keys().next().is_some() {
        doc! { field.into(): filter }
    } else {
        doc! {}
    }
}

As you can see, filter and filter_with are essentially copies of each other. The only difference is the (f)(t) in the match statements.

To me, this seemed like the perfect case for generalization. I tried to replace filter with this:

pub(super) fn filter2<S, R, T>(field: S, range: R) -> bson::Document
where
    S: Into<String>,
    R: RangeBounds<T>,
    T: 'static + Clone + Into<Bson>,
{
    filter_with(field, range, std::convert::identity)
}

As far as I can see, this should be identical to the original filter in function. However, I get this puzzling error when I try to compile:

error: implementation of `Fn` is not general enough
   --> ses/src/db/util.rs:110:5
    |
110 |     filter_with(field, range, std::convert::identity)
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Fn` is not general enough
    |
    = note: `fn(&'2 T) -> &'2 T {identity::<&'2 T>}` must implement `Fn<(&'1 T,)>`, for any lifetime `'1`...
    = note: ...but it actually implements `Fn<(&'2 T,)>`, for some specific lifetime `'2`

error: implementation of `FnOnce` is not general enough
   --> ses/src/db/util.rs:110:5
    |
110 |     filter_with(field, range, std::convert::identity)
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
    |
    = note: `fn(&'2 T) -> &'2 T {identity::<&'2 T>}` must implement `FnOnce<(&'1 T,)>`, for any lifetime `'1`...
    = note: ...but it actually implements `FnOnce<(&'2 T,)>`, for some specific lifetime `'2`

Note that Bson has a blanket From impl like so: Bson: From<&T> where T: Into<Bson> + Clone.

What causes this error? Where do the lifetimes '2 and '1 come from? As far as I can tell, std::convert::identity should work over all lifetimes. Why is filter2 different at all? It has identical behavior.

Extra weirdly, if I manually monomorphize filter_with using std::convert::identity, it compiles!

Upvotes: 1

Views: 23

Answers (0)

Related Questions