Reputation: 3131
@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