按字符串字段对结构向量进行排序

时间:2019-05-13 03:06:02

标签: rust

我在看似微不足道的按字符串字段排序方面遇到困难。复制如下:

struct Dummy {
    x: String,
    y: i8
}

fn main() {
    let mut dummies: Vec<Dummy> = Vec::new();
    dummies.push(Dummy { x: "a".to_string(), y: 1 });
    dummies.push(Dummy { x: "b".to_string(), y: 2 });

    dummies.sort_by_key(|d| d.x); // error[E0507]: cannot move out of borrowed content
    dummies.sort_by_key(|d| d.y); // This is fine
}

有人可以解释到底出了什么问题以及如何解决吗?错误描述是合理的,但还没有点击。

3 个答案:

答案 0 :(得分:3)

首先,让我们看看您的原始错误消息,然后我们将进行一些修复,并尝试理解所有内容。

dummies.sort_by_key(|d| d.x);中使用的闭包中,d是对Dummy实例的引用。但是,字段访问d.x本身就是String。如果您想返回该String,则必须将其所有权提供给所谓的闭包。但是由于d只是一个参考,因此您无法传递其数据的所有权。

一个简单的解决方法是简单地将字符串克隆为dummies.sort_by_key(|d| d.x.clone());。这将在关闭之前将字符串返回之前创建一个副本(这是Andra的解决方案)。效果很好,但是如果性能或内存使用成为问题,我们可以避免克隆。

这里的想法是使用字符串作为键很浪费。确实,我们需要知道的是两个字符串中的哪个较小。如果我们使用字符串作为键,那么每次sort函数需要比较两个Dummy时,它都会在每个函数上调用key函数,并将字符串传递给一个(非常短的)函数,该函数只对它们进行比较。如果我们在与借用相同的上下文中进行比较,则可以简单地传递比较结果,而不是字符串。

解决方案是对切片使用sort_by方法。这使我们可以引用两个Dummy,并确定一个是否小于另一个。例如,我们可以像dummies.sort_by(|d1, d2| d1.x.cmp(&d2.x)); (full example here)

这样使用它

附录

为什么不克隆sort_by_key就不能使用String?当然,必须有一些使用字符串切片和生存期的巧妙方法。

让我们看看the sort_by_key function.的签名

pub fn sort_by_key<K, F>(&mut self, f: F) where
    F: FnMut(&T) -> K,
    K: Ord, 

此功能有趣的部分不是那里,而是那里没有。类型参数K不取决于传递给f的引用的生存期。

在对切片进行排序时,将通过引用Dummy实例来重复调用键函数。由于切片在每次调用之间进行排序,因此引用的生存期必须非常短。如果更长,则下一次移动切片的元素时,它将失效。但是,K不能依赖该生存期。这意味着无论我们的关键功能是什么,它都不能返回依赖于Dummy当前位置的任何内容(例如字符串切片,引用或任何其他巧妙的构造方法 1 )。

但是,我们可以使K取决于传递给它的内容的生存时间。这里的想法是所谓的Higher-Rank Trait Bounds。这些仅在生命周期内有效(尽管理论上它们可以扩展到所有类型参数)。我们可以提出另一个带有签名的slice方法

fn sort_by_key_hrtb<T, F, K>(slice: &mut [T], f: F)
where
    F: Fn(&T) -> &K,
    K: Ord,

为什么这会使事情正常?在F: Fn(&T) -> &K,中,输出参考的生存期与输入参考的生存期隐含相同(或更长)。已终止,这是F: for<'a> Fn(&'a T) -> &'a K,,表示f应该能够获取具有任何生存期'a的引用,并返回具有生存期(大于或等于){{1} }。现在,我们有了一种可以完全按照您想要的方式使用的方法(讨厌的'a 2 除外)。 (playground link)


  1. 实际上,有一种(不安全的)聪明的构造可能可行,但我尚未对其进行审查。您可以在指向&的原始指针周围使用包装器,然后对该包装器使用String,以便它取消引用指针以进行比较。 3 的返回类型关键功能将是impl Ord,因此我们不需要任何生命周期。但是,这本质上是不安全的,我绝对不建议这样做。一个(可能)有效的示例是here

  2. 在这里我们需要使用*const String的唯一原因是&mut dummies实际上不是切片方法。如果是这样,sort_by_key_hrtb将自动借用并取消引用为片,因此我们可以像dummies这样调用该函数。

  3. 为什么用包装器而不只是指针? dummies.sort_by_key_hrtb(|d| &d.x);实现了*const T,但是它是通过比较地址而不是基础值(如果有的话)来实现的,这不是我们想要的。

答案 1 :(得分:1)

我认为这是因为它试图将<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <button type="button" id="btn-add-form">Add</button> <button type="button" id="btn-reset-form">Reset</button><br><input type="text" name="names[]" required> <input id="price" type="text" name="price[]" onkeyup="tot();" required> <input id="qty" type="text" name="qty[]" onkeyup="tot();" required> <input id="total" type="text" name="total[]" required> <div id="insert-form"></div>从一个结构移动到另一个结构。

这很好

String

该行为可能看起来像这样

struct Dummy {
    x: String,
    y: i8
}

fn main() {
    let mut dummies: Vec<Dummy> = Vec::new();
    dummies.push(Dummy { x: "a".to_string(), y: 1 });
    dummies.push(Dummy { x: "b".to_string(), y: 2 });

    dummies.sort_by_key(|d| d.x.clone()); // Clone the string
    dummies.sort_by_key(|d| d.y); // This is fine
}

像上面的示例一样使用struct Dummy { x: String, y: i8 } fn main() { let mut dummies: Vec<Dummy> = Vec::new(); dummies.push(Dummy { x: "a".to_string(), y: 1 }); dummies.push(Dummy { x: "b".to_string(), y: 2 }); let mut temp = Dummy{ x: "c".to_string(), y: 3 }; temp.x = dummies[0].x; // Error[E0507]: cannot move out of borrowed content }

clone()

答案 2 :(得分:1)

sort_by_key函数正在获取密钥的所有权:((JavaScriptExecutor)driver).executeScript("arguments[0].scrollIntoView(true);", element); https://doc.rust-lang.org/std/vec/struct.Vec.html#method.sort_by_key

这就是为什么您得到:https://doc.rust-lang.org/error-index.html#E0507

一个简单的解决方法是将引用存储在您的结构上,以便sort_by_key不会获取密钥的所有权。

然后,您需要使生命周期达到参考值,以便在您的结构消失后可以将其删除。

pub fn sort_by_key<K, F>(&mut self, f: F)