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

时间:2019-02-26 11:38:05

标签: reference rust immutability mutability

这里是Thing

struct Thing(i32);

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

这是一个试图使Thing变异并返回true或false的函数,具体取决于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是有意义的。请注意,它应该是一个不可变引用,因为我不希望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 引用传递了,这不是我想要的

  • 我可以通过向Option重新分配None来突变(*handle) = None;,打破随后的所有突变。 (例如,取消注释第12行(&mut)。
  • 这很混乱:周围有很多无关的ref mut
  • 这真是一团糟:天堂只知道为什么我必须在if let语句中使用&mut,而惯例是在其他任何地方都使用Option
  • 它破坏了在编译器中具有复杂的借位检查和可变性检查规则的目的。

有什么方法可以真正实现我想要的:通过引用传递不可变的<input type="radio" name="user" value="1" data-active="0"> User 1 <input type="radio" name="user" value="2" data-active="0"> User 2 并真正能够使用其内容?

2 个答案:

答案 0 :(得分:3)

您不能从不可变的引用中提取可变的引用,即使是对其内部的引用。这就是重点!允许使用不可变引用的多个别名,因此,如果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);
}

答案 1 :(得分:1)

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

与@Peter Hall和@Stargateur讨论之后,我开始理解为什么我需要在各处使用&mut Option<&mut Thing>RefCell<>也是可行的解决方法,但它不会整洁,也无法真正实现我最初试图实现的模式。

问题是这样的:如果一个人被允许变异一个对象,而该对象仅具有对Option<&mut T> immutable 引用,则可以使用此功能完全打破借用规则。具体而言,从本质上讲,您可以对同一个对象有许多 可变 引用,因为您可以有许多这样的不可变引用。

知道,只有一个 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. try_increment_twice()拥有Option<>
  3. 的所有权
  4. try_increment() 必须Option<>设为&mut,以便编译器知道在此期间,它具有对Option<>的唯一可变引用。通话
  5. 如果编译器知道try_increment()是对Option<>的唯一可变引用,而该引用具有对Thing的唯一可变引用,则编译器会知道没有违反借用规则。

另一个实验

Option<>的可变性问题仍然存在,因为人们可以打电话给take()等。在易变的Option<>上,破坏了随后的一切。

要实现我想要的模式,我需要类似于{em> 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>