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

标签 reference rust immutability mutability

这是一个东西:

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 引用传递,这不是我想要的:

  • 我可以通过将 None 重新分配给它来改变 Option,从而破坏所有后续突变。 (取消注释第 12 行 ((*handle) = None;) 例如。)
  • 很乱:到处都是大量无关的 &mut
  • 这是双重困惑:天知道为什么我必须在 if let 语句中使用 ref mut 而约定是在所有地方使用 &mut否则。
  • 它违背了在编译器中使用复杂的借用检查和可变性检查规则的目的。

有没有什么方法可以真正实现我想要的:通过引用传递一个不可变的 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);
}

关于reference - 如何通过引用使用包含可变引用的不可变选项?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54884678/

相关文章:

rust - 是否可以有一个带有泛型的结构定义,其中类型既是特征类型又具有生命周期?

rust - 如何复制而不是借用 i64 到 Rust 的闭包中?

algorithm - 一种用可变或不可变状态替换序列中事件的有效技术

python - 在方法中编辑字典值

php - 在 Php 中将用户数据作为对 array_walk_recursive 的引用传递

reference - 为什么我可以返回对局部文字的引用而不是变量?

c# - 登录 Visual Studio 2010 后无法运行代码的原因是什么?

string - 在 Rust 中,有没有办法使用 Windows 约定在 r## #"..."### 中创建文字换行符?

scala - 当声明为var时,Scala不可变集是可变的

javascript - 数组文字作为对象键?