借用检查器检查可变引用的最佳做法是什么?

时间:2017-03-05 05:56:52

标签: rust immutability borrow-checker

我想获取一个链表并用结构实例填充它,但前提是该列表还没有包含我正在考虑添加的项目。

我正在使用积分,所以如果(3,5)在列表中我不想添加它,否则我会这样做。

我目前的代码:

use std::collections::LinkedList;

struct Location {
    x: i32,
    y: i32,
}

fn main() {
    let mut locations = LinkedList::new();

    loop {
        let location_set = &mut locations;
        // Scanner stuff happens.
        if !has_location(location_set, &next_checkpoint_x, &next_checkpoint_y) {
            let point = Location {
                x: next_checkpoint_x,
                y: next_checkpoint_y,
            };
            locations.push_back(point);
        }
    }
}

fn has_location(location_list: LinkedList<Location>, target_x: &i32, target_y: &i32) -> bool {
    true // Just until I can figure out this mutability stuff
}

我已经能够通过这些变化来运行它,但这对我来说似乎不对。

loop {
    if !has_location(&mut locations, &next_checkpoint_x, &next_checkpoint_y) {
        // stuff
    }
}
fn has_location(location_list: &LinkedList<Location>, target_x: &i32, target_y: &i32) -> bool {
    true
}

我不希望has_location能够改变链接列表,我只是希望它能够借用它以便它可以查看它的内部。我不想考虑影响它检查的链表的has_location(或类似功能)。这就是我创建location_set的原因。我想要一些东西以只读的方式引用位置,并且要传递给has_location函数,以及在调用has_location函数后被引用的(位置)不被破坏的位置。我在函数调用中传递了&参数,因为我不希望传递的参数被破坏 - 所以我想借用它们?

我想要的东西是否有意义 - 如果我最初将位置声明为可变链表,我可以将其不可变版本传递给要评估的函数吗?

1 个答案:

答案 0 :(得分:2)

如果您有可变借款,您可以随时重新借用它作为不可变借款。这甚至会隐式发生(&mut T将强制转换为&T)。因此,您可以直接将location_set传递给has_location。或者,如果您想明确该函数不会改变其参数,您可以编写&*location_set而不是location_set(尽管我认为这是不必要的)。

另外,请注意,在存在不可变借入的情况下,您不能使用可变借入;不可变借用在数据结构范围内冻结数据结构。同样,你不能使用变量,而在范围内对该变量进行可变借用。在您的第一个代码示例中,当locations在范围内时,您无法引用location_set,因为location_setlocations上进行了可变借用,但您可以再次使用location_set,因为push_back只需要一个可变的借位(它不会按值LinkedList)。

仅检查数据结构的函数通常会接收到数据结构的不可变借位。如果数据结构是通过值传递的,那么函数将取得它的所有权,因此在返回之前将其销毁(除非它被移动到别处)。因此,是的,您希望has_location接受LinkedList的不可变借入。通过接受不可变借用(而不是可变借用),编译器将阻止您修改LinkedList(除非您使用不安全的代码)。

全部放在一起:

use std::collections::LinkedList;

struct Location {
    x: i32,
    y: i32,
}

fn main() {
    let mut locations = LinkedList::new();
    let next_checkpoint_x = 0;
    let next_checkpoint_y = 0;

    loop {
        let location_set = &mut locations;
        // Scanner stuff happens.
        if !has_location(location_set, &next_checkpoint_x, &next_checkpoint_y) {
            let point = Location { x: next_checkpoint_x, y: next_checkpoint_y };
            location_set.push_back(point);
        }
    }
}

fn has_location(location_list: &LinkedList<Location>, target_x: &i32, target_y: &i32) -> bool {
    true
}
  

我不明白的是,在你的例子中,location_set直接传递给has_location(所以函数拥有它,对吧?)。这在我看来意味着在has_location范围的末尾,location_set应该被销毁,不是吗?如何在location_set块中继续使用if

不,has_location不拥有location_set。如果这是任何其他未实现Copy的类型(例如String),那么您就是对的,但引用有特殊规则可以使它们更方便使用。

当您传递对函数的引用时,编译器将自动重新启动该引用以生成新引用,通常具有更短的生命周期。在这里,编译器重新借用可变引用并生成不可变引用;在不可变引用超出范围之前,不能使用可变引用(不可变引用未绑定到此处的变量,因此您不会注意到这一点)。从概念上讲,它就好像你传递了一个不可变引用的可变引用(在Rust中,& &mut T不允许你改变T,因为可能有多个副本那个外部参考文献),它只是两个参考文献被压平了#34;。

  

此外,如果location_set是不可变的,push_back如何能够添加到列表的末尾,是因为该函数将可变借入强制转换为不可变借用?

location_set仍然是一个可变引用(因为我们使用&mut运算符创建它)。 has_location对不可变引用进行操作的事实并未改变location_set是可变引用的事实。一旦评估了对has_location的调用,location_set可以作为可变引用重用,因此允许push_back等变异操作。

请记住,可变性是Rust纯粹是一个编译时的概念; mut或缺少mut只是让编译器验证您的代码不会进行非法操作,但是一旦编译完代码,这些标记就无处可见。