使用std::convert::identity时的Rust生命周期错误排查
Rust中
Fn实现不够通用的错误分析与解决 问题概述
尝试用std::convert::identity作为映射函数,通过filter_with重构原有的filter函数时,编译报错提示Fn/FnOnce实现不够通用。即使手动单态化filter_with能正常编译,但无法理解错误产生的原因。
最小复现示例
复现代码
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 }
编译错误
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`
原始业务场景
原filter函数
该函数用于将RangeBounds转换为MongoDB风格的过滤器:
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! {} } }
新增的filter_with函数
支持传入映射函数,将范围值转换为数据库兼容格式:
/// 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! {} } }
重构的filter2函数
尝试用filter_with+identity替代原filter,编译报错:
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) }
对应的编译错误
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`
错误原因
核心问题出在生命周期的通用性要求上:
filter_with的约束F: Fn(&I) -> O,实际上隐含要求闭包能接受任意生命周期的&I引用(即对于调用方传入的任何'1,都要满足Fn(&'1 I) -> O)。- 当传入
std::convert::identity时,编译器自动推断出的类型是fn(&'2 T) -> &'2 T,这里的'2是某个特定的生命周期,无法满足“任意生命周期”的要求,因此报错。 - 而你自定义的
id函数,签名是for<'a> fn(&'a T) -> &'a T,天生支持任意生命周期,所以能通过编译。手动单态化时,编译器会强制使用通用生命周期版本,因此也能正常编译。
解决方法
方法1:给filter_with添加高阶生命周期约束
修改filter_with的trait bound,明确要求闭包支持任意生命周期的引用:
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>, // 使用HRTB(高阶生命周期)强制闭包支持任意生命周期 F: for<'a> Fn(&'a I) -> O, { // 函数体不变 }
方法2:显式指定identity的通用类型
在调用filter_with时,显式指定identity的类型为通用版本,让编译器推断出正确的生命周期:
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::<&T>) }
方法3:使用自定义的identity函数
直接使用你自己定义的id函数,它天然支持任意生命周期:
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, id) }
验证
采用上述任一方法修改后,filter2都能正常编译,且功能与原filter完全一致。
内容的提问来源于stack exchange,提问作者Carson
相关产品推荐
相关产品推荐

