我在看似微不足道的按字符串字段排序方面遇到困难。复制如下:
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
}
有人可以解释到底出了什么问题以及如何解决吗?错误描述是合理的,但还没有点击。
答案 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)
实际上,有一种(不安全的)聪明的构造可能可行,但我尚未对其进行审查。您可以在指向&
的原始指针周围使用包装器,然后对该包装器使用String
,以便它取消引用指针以进行比较。 3 的返回类型关键功能将是impl Ord
,因此我们不需要任何生命周期。但是,这本质上是不安全的,我绝对不建议这样做。一个(可能)有效的示例是here。
在这里我们需要使用*const String
的唯一原因是&mut dummies
实际上不是切片方法。如果是这样,sort_by_key_hrtb
将自动借用并取消引用为片,因此我们可以像dummies
这样调用该函数。
为什么用包装器而不只是指针? 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)