问题描述
虽然我理解 Rust 的内存安全方法、所有权概念,并且对引用没有任何问题,但我很难弄清楚 Rust 如何让我解决一个看似微不足道的问题。
一个结构:
pub struct S {
value: Option<i8>,}
use rand::Rng;
impl S {
fn get_value(&mut self) -> i8 {
if let Some(v) = self.value {
return v;
}
let v = rand::thread_rng().gen::<i8>();
self.value = Some(v);
v
}
}
当然,get_value()
需要一个可变引用 &mut self
,因为它修改了 self
。
当我想根据 S
的结果对 get_value()
的引用向量进行排序时,这种可变性是我挣扎的根源。因为我想按 get_value()
排序,所以 sort_by
使用的比较函数将需要可变引用。
我的第一次尝试:
fn main() {
let mut a = S {value: None};
let mut b = S {value: None};
let mut c = S {value: None};
let mut v = vec![&mut a,&mut b,&mut c];
v.sort_by( |a,b| a.get_value().cmp(&b.get_value()) );
}
抛出:
error[E0596]: cannot borrow `**a` as mutable,as it is behind a `&` reference
--> src/main.rs:27:20
|
27 | v.sort_by( |a,b| a.get_v().cmp(&b.get_v()) );
| - ^ `a` is a `&` reference,so the data it refers to cannot be borrowed as mutable
| |
| help: consider changing this to be a mutable reference: `&mut &mut S`
我最初的想法是,首先拥有一个可变引用向量将允许比较函数使用可变引用。但是,我的 getter 函数借用了对可变引用的可变引用,这就是错误显示 cannot borrow '**a'
的原因。
帮助建议将 |a,b|
更改为 &mut &mut S
引用。
&mut &mut
和 &&mut
一样吗?
这是否意味着我应该将其更改为 |mut a:&&mut S,mut b:&&mut S|
?
解决方法
假设您的示例已简化,并且在您的实际代码中实际上需要可变性,这可能是内部可变性的好例子,例如带有Cell
:
use std::cell::Cell;
pub struct S {
value: Cell<Option<i8>>,}
这意味着你可以更新你的 getter 方法,这样它就不需要可变的自引用:
impl S {
fn get_value(&self) -> i8 {
if let Some(v) = self.value.get() {
return v;
}
let v = rand::thread_rng().gen::<i8>();
self.value.set(Some(v));
v
}
}
你也可以去掉一些其他的可变引用:
fn main() {
let a = S { value: Cell::default() };
let b = S { value: Cell::default() };
let c = S { value: Cell::default() };
let mut v = vec![&a,&b,&c];
v.sort_by(|a,b| a.get_value().cmp(&b.get_value()));
}
,
sort_by(<closure>)
中的闭包具有签名 FnMut(&T,&T) -> Ordering
,因为项目在排序期间必须是不可变的。如果可以在排序过程中改变项目,那么您将如何判断集合何时实际排序?验证排序的正确性变得不可能!
此外,sort_by
是一种基于比较的排序,这意味着它将至少访问集合中的每个项目一次,因此 每个 项目将在整个排序过程中被初始化,因此您在这种情况下使用延迟初始化 getter 并没有真正获得任何好处。
如果出于某种原因你想坚持使用延迟初始化 getter 则我的建议是在排序之前初始化所有项目:
use rand::Rng;
pub struct S {
value: Option<i8>,}
impl S {
fn get_value(&mut self) -> i8 {
if let Some(v) = self.value {
return v;
}
let v = rand::thread_rng().gen::<i8>();
self.value = Some(v);
v
}
}
fn main() {
let mut a = S { value: None };
let mut b = S { value: None };
let mut c = S { value: None };
let mut v = vec![&mut a,&mut b,&mut c];
// initialize all items,it's what would
// happen in the sort below if it allowed
// mutating items mid-sort anyway
v.iter_mut().for_each(|i| {
i.get_value();
});
// sort initialized items without mutating them
v.sort_by(|a,b| a.value.cmp(&b.value));
}