如何通过引用使用包含可变引用的不可变选项?

How can you use an immutable Option by reference that contains a mutable reference?

这是 Thing:

struct Thing(i32);

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

这里有一个函数试图改变 Thing 和 returns 是真还是假,这取决于 Thing 是否可用:

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

如上所述,it works just fine (link to Rust playground)。输出如下:

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) 放弃了 handle 的所有权,因此第二次调用是非法的。通常情况下,Rust 编译器会产生一条合理的错误消息:

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

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

我的问题是我不知道该怎么做。

这里is the closest working version that I could get:

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 现在由 mutable 引用传递,即 not我想要什么:

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

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

Rust 为 interior mutability 提供了几个逃生口,例如 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);
}

TL;DR:答案是,我不能。

经过与@Peter Hall 和@Stargateur 的讨论,我明白了为什么我需要到处使用 &mut Option<&mut Thing>RefCell<> 也是一个可行的解决方法,但它并不简洁,也没有真正实现我最初寻求实现的模式。

问题是这样的:如果一个人被允许改变一个只有 不可变 引用的对象 Option<&mut T> 一个人可以使用这个权力来破坏完全借用规则。具体来说,本质上,您可以对同一对象有许多 mutable 引用,因为您可以有许多这样的不可变引用。

知道只有一个可变引用到Thing(由Option<>拥有)但是,一旦我开始引用 Option<>,编译器就不再知道这些引用并不多了。

最佳版型如下:

fn try_increment(handle: &mut Option<&mut Thing>) -> bool {
    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);
}

备注:

  1. Option<> 持有对 Thing
  2. 的唯一现存可变引用
  3. try_increment_twice() 获得 Option<>
  4. 的所有权
  5. try_increment() 必须Option<> 作为 &mut 以便编译器知道它具有对 [= 的唯一可变引用17=], 通话中
  6. 如果编译器知道 try_increment() 具有对 Option<> 的唯一可变引用,而 Option<> 包含对 Thing 的唯一可变引用,则编译器知道借用规则没有被侵犯了。

另一个实验

Option<> 的可变性问题仍然存在,因为可以调用 take() 等。在一个可变的 Option<> 上,打破了后面的一切。

为了实现我想要的模式,我需要 类似 Option<> 的东西,但是,即使它是 mutable,它不能被改变。像这样:

struct Handle<'a> {
    value: Option<&'a mut Thing>,
}

impl<'a> Handle<'a> {
    fn new(value: &'a mut Thing) -> Self {
        Self {
            value: Some(value),
        }
    }

    fn empty() -> Self {
        Self {
            value: None,
        }
    }

    fn try_mutate<T, F: Fn(&mut Thing) -> T>(&mut self, mutation: F) -> Option<T> {
        if let Some(ref mut v) = self.value {
            Some(mutation(v))
        }
        else {
            None
        }
    }
}

现在,我想,我可以整天传递 &mut Handle 并且知道拥有 Handle 的人只能改变其内容,而不是句柄本身。 (See Playground)

不幸的是,即使这样也没有任何好处,因为如果你有一个可变引用,你总是可以用取消引用运算符重新分配它:

fn try_increment(handle: &mut Handle) -> bool {
    if let Some(_) = handle.try_mutate(|t| { t.increment_self() }) {
        // This breaks future calls:
        (*handle) = Handle::empty();

        true
    }
    else {
        println!("warning: increment failed");
        false
    }
}

一切都很好。

底线结论:只需使用&mut Option<&mut T>