首页 > 解决方案 > 如何通过引用使用包含可变引用的不可变选项?

问题描述

这是一个Thing

struct Thing(i32);

impl Thing {
    pub fn increment_self(&mut self) {
        self.0 += 1;
        println!("incremented: {}", self.0);
    }
}

这是一个尝试改变 aThing并返回 true 或 false 的函数,具体取决于 aThing是否可用:

fn try_increment(handle: Option<&mut Thing>) -> bool {
    if let Some(t) = handle {
        t.increment_self();
        true
    } else {
        println!("warning: increment failed");
        false
    }
}

这是一个使用示例:

fn main() {
    try_increment(None);

    let mut thing = Thing(0);
    try_increment(Some(&mut thing));
    try_increment(Some(&mut thing));

    try_increment(None);
}

如上所述,它工作得很好(链接到 Rust 游乐场)。下面的输出:

warning: increment failed
incremented: 1
incremented: 2
warning: increment failed

当我想编写一个改变Thing两次的函数时,问题就出现了。例如,以下内容不起作用:

fn try_increment_twice(handle: Option<&mut Thing>) {
    try_increment(handle);
    try_increment(handle);
}

fn main() {
    try_increment_twice(None);

    let mut thing = Thing(0);
    try_increment_twice(Some(&mut thing));

    try_increment_twice(None);
}

这个错误很有意义。第一次调用给try_increment(handle)了away的所有权,handle所以第二次调用是非法的。通常情况下,Rust 编译器会产生一个合理的错误消息:

   |
24 |     try_increment(handle);
   |                   ------ value moved here
25 |     try_increment(handle);
   |                   ^^^^^^ value used here after move
   |

为了解决这个问题,我认为handle通过引用传递是有意义的。请注意,它应该是一个不可变的引用,因为我不希望try_increment能够改变handle自己(例如分配None给它)只能调用它的值的突变。

我的问题是我无法弄清楚如何做到这一点。

是我能得到的最接近的工作版本

struct Thing(i32);

impl Thing {
    pub fn increment_self(&mut self) {
        self.0 += 1;
        println!("incremented: {}", self.0);
    }
}

fn try_increment(handle: &mut Option<&mut Thing>) -> bool {
    // PROBLEM: this line is allowed!
    // (*handle) = None;

    if let Some(ref mut t) = handle {
        t.increment_self();
        true
    } else {
        println!("warning: increment failed");
        false
    }
}

fn try_increment_twice(mut handle: Option<&mut Thing>) {
    try_increment(&mut handle);
    try_increment(&mut handle);
}

fn main() {
    try_increment_twice(None);

    let mut thing = Thing(0);
    try_increment_twice(Some(&mut thing));

    try_increment_twice(None);
}

该代码按预期运行,但Option现在通过可变引用传递,这不是我想要的:

有什么方法可以真正实现我想要的:通过Option引用传递一个不可变对象,并且实际上能够使用它的内容?

标签: referencerustimmutabilitymutability

解决方案


您无法从不可变引用中提取可变引用,即使是对其内部的引用。这就是重点!不可变引用的多个别名是允许的,因此,如果 Rust 允许你这样做,你可能会遇到两段代码能够同时改变相同数据的情况。

Rust 为内部可变性提供了几个逃生舱口,例如RefCell

use std::cell::RefCell;

fn try_increment(handle: &Option<RefCell<Thing>>) -> bool {
    if let Some(t) = handle {
        t.borrow_mut().increment_self();
        true
    } else {
        println!("warning: increment failed");
        false
    }
}

fn try_increment_twice(handle: Option<RefCell<Thing>>) {
    try_increment(&handle);
    try_increment(&handle);
}

fn main() {
    let mut thing = RefCell::new(Thing(0));
    try_increment_twice(Some(thing));
    try_increment_twice(None);
}

推荐阅读