为啥我不能在使用 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`

我可以通过将键类型固定为&amp;T 而不是使用泛型参数K 或使用&amp;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 以使返回类型为&amp;'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_bysort_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 对向量进行排序时使用返回引用的键函数?的主要内容,如果未能解决你的问题,请参考以下文章

CUDA:如何在 GPU 上直接使用推力::sort_by_key? [复制]

为啥我不能将整数向量推入 C++ 中的二维整数向量?

为啥 R 的 ifelse 语句不能返回向量?

使用光流的汽车跟踪。为啥向量不能正确绘制

为啥我不能返回这个向量向量?

为啥我的编译器对 c 字符串类型的向量存在内存问题?