在闭包中借用可变引用来对向量进行排序

Borrowing mutable references in closure to sort vector

虽然我理解 Rust 的内存安全方法、所有权概念,并且对一般引用没有问题,但我正在努力弄清楚 Rust 如何让我解决一个看似微不足道的问题。

此代码是一个最小示例。

一个结构:

pub struct S {
    value: Option<i8>,
}

第一次计算值的getter,然后returns存储的值:

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.

当我想根据 get_value() 的结果对 S 的引用向量进行排序时,这种可变性是我苦苦挣扎的根源。因为我想按 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|

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));
}

playground

假设您的示例已简化,并且在您的实际代码中实际上需要可变性,这可能是内部可变性的一个很好的例子,例如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()));
}