为啥我不能在使用 sort_by_key 对向量进行排序时使用返回引用的键函数?
Posted
技术标签:
【中文标题】为啥我不能在使用 sort_by_key 对向量进行排序时使用返回引用的键函数?【英文标题】:Why can't I use a key function that returns a reference when sorting a vector with sort_by_key?为什么我不能在使用 sort_by_key 对向量进行排序时使用返回引用的键函数? 【发布时间】:2018-04-17 17:35:31 【问题描述】:我正在尝试使用返回对向量中字符串的引用的键函数对Vec<String>
进行排序。一个人为的例子是使用标识函数作为关键函数(这当然没用,但它是重现我的问题的最小例子):
fn key(x: &String) -> &String
x
现在给定items: Vec<String>
,我希望能够做到
items.sort_by_key(key);
这会产生以下错误:
error[E0271]: type mismatch resolving `for<'r> <fn(&std::string::String) -> &std::string::String main::key as std::ops::FnOnce<(&'r std::string::String,)>>::Output == _`
--> src/main.rs:19:11
|
19 | items.sort_by_key(key);
| ^^^^^^^^^^^ expected bound lifetime parameter, found concrete lifetime
|
= note: concrete lifetime that was found is lifetime '_#16r
我不明白为什么会出现这个错误,所以我试图追查这个问题。我首先实现了我自己版本的sort_by_key()
:
fn sort_by_key<T, K: Ord>(a: &mut [T], key: fn(&T) -> K)
a.sort_by(|x, y| key(x).cmp(&key(y)));
当试图调用这个函数时,我得到了看起来像“相反”的错误:
error[E0308]: mismatched types
--> src/main.rs:22:29
|
22 | sort_by_key(&mut items, key);
| ^^^ expected concrete lifetime, found bound lifetime parameter
|
= note: expected type `fn(&std::string::String) -> _`
found type `fn(&std::string::String) -> &std::string::String main::key`
我可以通过将键类型固定为&T
而不是使用泛型参数K
或使用&K
而不是K
作为键函数的返回类型来编译此代码:
fn sort_by_key_v2<T: Ord>(a: &mut [T], key: fn(&T) -> &T)
a.sort_by(|x, y| key(x).cmp(&key(y)));
fn sort_by_key_v3<T, K: Ord>(a: &mut [T], key: fn(&T) -> &K)
a.sort_by(|x, y| key(x).cmp(&key(y)));
我也尝试添加生命周期注释,但这只会转移错误而没有解决它。
Here's the three versions of the sort_by_key()
function on the Playground.
为什么会出现这些错误?有什么办法可以修复它们,同时保持密钥类型 K
完全通用?
【问题讨论】:
@E_net4 我添加了一个游乐场链接,并简单地删除了最后一个问题。我认为在一篇文章中提出多个问题是完全可以接受的,只要它们相互依赖即可。 @E_net4 我听从了你的建议并删除了枚举。我的问题本质上是为什么我会收到错误以及如何修复它们。 【参考方案1】:现在,您必须使用“长”形式:
v.sort_by(|x, y| key(x).cmp(&key(y)));
为什么会出现这些错误?有什么办法可以解决吗?
原因和解决方法是一回事:Rust 目前的表达能力不足以表达你想要的。所需的功能称为generic associated types (GATs);以前称为关联类型构造函数 (ATC) 或更高种类的类型 (HKT)。
来自associated issue:
为了使
sort_by_key
调用正常,需要将输入引用[...] 的生命周期并入B
以使返回类型为&'a str
,但B
是一个类型参数。
我不知道sort_by_key
的签名在实现时是否能够无缝移动到 GAT。
在您控制所有类型的签名的类似情况下,您可以要求返回引用:
use std::cmp::Ordering;
struct User
name: String,
fn compare_keys<T, R>(a: T, b: T, key: impl Fn(&T) -> &R) -> Ordering
where
for<'a> &'a R: Ord,
let ak = key(&a);
let bk = key(&b);
ak.cmp(&bk)
fn main()
let alice = User
name: String::from("alice"),
;
let bob = User
name: String::from("bob"),
;
compare_keys(alice, bob, |u| &u.name);
这是不理想的,因为现在您无法返回非引用,但在实施 GAT 之前根本没有完整的解决方案。根据您的情况,您可以添加类似sort_by
和sort_by_key
的并行方法。
【讨论】:
谢谢,我已经怀疑这是不可能的。我将阅读链接的 RFC 以了解错误消息的含义,以及为什么该方法的错误消息与我自己的版本不同。【参考方案2】:作为@Shepmaster explained,您不能让sort_by_key
函数处理key
函数的返回类型的通用关联生命周期,但这里是一个始终返回引用的键函数的变体:
fn sort_by_key_ref<T, F, K>(a: &mut [T], key: F)
where
F: Fn(&T) -> &K,
K: ?Sized + Ord,
a.sort_by(|x, y| key(x).cmp(key(y)));
您还可以写下关键功能的生命周期要求:
for<'a> F: Fn(&'a T) -> &'a K,
见example on playground。
【讨论】:
谢谢,我已经想通了(请参阅我的问题中的第三个版本)。我仍然想了解错误消息的确切含义。 嗯,完全错过了,对不起。至少?Sized
部分添加了一些新内容:)【参考方案3】:
基于 Shepmaster 的回答,最干净的解决方法 (IMO) 是:
fn ref_key<T, K: Ord + ?Sized>(mut f: impl FnMut(&T) -> &K) -> impl FnMut(&T, &T) -> Ordering
move |a, b| f(a).cmp(f(b))
只需将slice.sort_by_key(key)
转换为slice.sort_by(ref_key(key))
即可使用。
【讨论】:
以上是关于为啥我不能在使用 sort_by_key 对向量进行排序时使用返回引用的键函数?的主要内容,如果未能解决你的问题,请参考以下文章