简体   繁体   English

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

[英]Borrowing mutable references in closure to sort vector

Although I understand Rust's approach to memory safety, the ownership concepts, and have no problem with references in general, I'm struggling to figure out how Rust wants me to solve a seemingly trivial problem.尽管我了解 Rust 对 memory 安全性、所有权概念的方法,并且通常对引用没有问题,但我仍在努力弄清楚 Rust 如何让我解决一个看似微不足道的问题。

This code is a minimal example.此代码是一个最小示例。

A struct:一个结构:

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

with a getter that computes a value the first time, then returns the stored value:使用第一次计算值的 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
    }
}

Of course, get_value() needs a mutable reference &mut self , because it modifies self .当然, get_value()需要一个可变引用&mut self ,因为它修改了self

This mutability is the source of my struggling when I want to sort a vector of references to S , by the result of get_value() .当我想通过get_value()的结果对S的引用向量进行排序时,这种可变性是我苦苦挣扎的根源。 Because I want to sort by get_value() , the comparison function used by sort_by will need mutable references.因为我想按get_value()排序,所以sort_by使用的比较 function 将需要可变引用。

My first attempt:我的第一次尝试:

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

Throws:抛出:

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`

My initial thought was that having a vector of mutable references in the first place would allow the comparison function to use mutable references.我最初的想法是,首先拥有一个可变引用向量将允许比较 function 使用可变引用。 My getter function however borrows a mutable reference to a mutable reference, that's why the error says cannot borrow '**a' .我的吸气剂 function 但是借用了对可变引用的可变引用,这就是错误说cannot borrow '**a'原因。

The help suggests to change |a,b|帮助建议更改|a,b| so that they are &mut &mut S references.所以它们是&mut &mut S引用。

Is &mut &mut the same as &&mut ? &mut &mut&&mut一样吗? Does it mean I should change it into |mut a:&&mut S, mut b:&&mut S|这是否意味着我应该将其更改为|mut a:&&mut S, mut b:&&mut S| ? ?

Assuming your example is simplified, and the mutability is actually necessary in your real code, this might be a good case for interior mutability, eg with a Cell :假设您的示例已简化,并且在您的真实代码中实际上需要可变性,这可能是内部可变性的一个很好的例子,例如使用Cell

use std::cell::Cell;

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

Which means you can update your getter method so it doesn't take a mutable self reference:这意味着您可以更新您的 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
    }
}

And you can get rid of some of the other mutable references too:您也可以摆脱其他一些可变引用:

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

The closure in sort_by(<closure>) has the signature FnMut(&T, &T) -> Ordering because the items must be immutable for the duration of the sort. sort_by(<closure>)中的闭包具有签名FnMut(&T, &T) -> Ordering ,因为项目在排序期间必须是不可变的。 If it was possible to mutate items during the sort then how would you ever be able to tell when the collection is actually sorted?如果可以在排序过程中改变项目,那么您将如何知道集合何时实际排序? It becomes impossible to verify the correctness of the sort!无法验证排序的正确性!

Furthermore, sort_by is a comparison-based sort which means it will visit every item in the collection at least once, so every item will be initialized throughout the sort, so you're not really gaining anything by using a lazy-initialization getter in this scenario.此外, sort_by是一种基于比较的排序,这意味着它将至少访问集合中的每个项目一次,因此每个项目都将在整个排序过程中被初始化,因此在此使用惰性初始化 getter 并没有真正获得任何东西设想。

If for some reason you want to stick with the lazy-initialization getter for other reasons then my suggestion is just to initialize all the items prior to sorting them:如果出于某种原因您想坚持使用延迟初始化 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 操场

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM