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

问题描述

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

代码一个最小的示例。

一个结构:

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

带有一个第一次计算一个值,然后返回存储的值的getter:

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

playground