【问题标题】:How can I pattern match a tuple containing a &mut enum and use the enum in the match arm?如何对包含 &mut 枚举的元组进行模式匹配并在匹配臂中使用该枚举?
【发布时间】:2018-07-13 16:33:05
【问题描述】:

如何编译下面的代码?它看起来非常安全,但无法说服编译器。

匹配*self的版本给出错误:cannot move out of borrowed content在匹配的行

匹配self的版本给出:use of moved value: *self

enum Foo {
    Foo1(u32),
    Foo2(i16),
}

impl Foo {
    fn bar(&mut self, y: u32) -> (u32, &mut Foo) {
        match (*self, y) {
            (Foo::Foo1(ref mut a), b) if (b == 5) => {
                print!("is five");
                *a = b + 42;

                (b, self)
            }

            (Foo::Foo2(ref mut a), b) if (b == 5) => {
                print!("is five");
                *a = (b + 42) as i16;

                (*a * b, self)
            }

            _ => {
                print!("is not five!");
                (y, self)
            }
        }
    }
}

我觉得我需要一个如下所示的匹配臂,但它似乎不是有效的语法:

(ref mut f @ Foo::Foo1, b) if (b == 5) => {
    print!("is five");
    f.0 = b + 42;
    (b, f)
} 
error[E0532]: expected unit struct/variant or constant, found tuple variant `Foo::Foo1`
  --> src/main.rs:24:30
   |
24 |                 (ref mut f @ Foo::Foo1, b) if (b == 5) => {
   |                              ^^^^^^^^^ not a unit struct/variant or constant

【问题讨论】:

  • “看起来非常安全”,我的 C 代码看起来也非常安全,但 rust 不是关于“似乎”而是“是”
  • 我现在正在阅读链接的问题,但您的解决方案仍然给我use of moved value: *self
  • 请注意,我也在尝试返回 self,发生错误的地方

标签: rust pattern-matching borrow-checker


【解决方案1】:

不,这不安全。您正在尝试在匹配臂中引入 可变别名。可变引用a 指向与self 相同的值。可以更改 self(例如 *self = Foo::Foo1(99)),这会使 a 无效,因此不允许使用此代码。

相反,可变地重新借用 selfmatch 语句中并让它返回元组的第一个值。由于该值没有对self 的引用,因此您可以使用match 的结果返回self

enum Foo {
    Foo1(u32),
    Foo2(u32), // changed so I don't have to figure out what casting you meant
}

impl Foo {
   fn bar(&mut self, y: u32) -> (u32, &mut Foo) {
        let next = match (&mut *self, y) {
            (Foo::Foo1(a), b @ 5) => {
                *a = b + 42;
                b
            }

            (Foo::Foo2(a), b @ 5) => {
                *a = b + 42;
                *a * b
            }

            _ => y,
        };

        (next, self)
    }
}

但是,像这样返回self 在这里毫无意义。调用者已经有一个&mut Foo,所以你不需要“还给它”。这允许简化为:

impl Foo {
    fn bar(&mut self, y: u32) -> u32 {
         match (self, y) {
            (Foo::Foo1(a), b @ 5) => {
                *a = b + 42;
                b
            }

            (Foo::Foo2(a), b @ 5) => {
                *a = b + 42;
                *a * b
            }

            _ => y,
        }
    }
}

我还是会说这是一个安全的操作,尽管编译器可能无法理解

使用non-lexical lifetimes,借阅检查器变得更加智能。添加显式重借的原始代码编译:

#![feature(nll)]

enum Foo {
    Foo1(u32),
    Foo2(u32), // changed so I don't have to figure out what casting you meant
}

impl Foo {
   fn bar(&mut self, y: u32) -> (u32, &mut Foo) {
        match (&mut *self, y) {
            (Foo::Foo1(a), b @ 5) => {
                *a = b + 42;
                (b, self)
            }

            (Foo::Foo2(a), b @ 5) => {
                *a = b + 42;
                (*a * b, self)
            }

            _ => (y, self),
        }
    }
}

另见:

【讨论】:

  • 感谢您的输入 - 尽管别名仅在返回期间发生,所以我仍然会说这是一个安全的操作,尽管编译器可能无法理解这一点。但是,您的回答还指出,我的示例过于愚蠢-在实际情况下,数据结构是自引用的,而我实际上正在返回原始子树的子树,我将调整我的问题以更好地反映这一点跨度>
  • @ÁkosVandra 编辑问题以使现有答案无效通常是不受欢迎的。
  • 抱歉,恢复了更改,并在这里提交了一个新问题:stackoverflow.com/questions/51330147/…
  • @ÁkosVandra 和我对 NLL 的编辑并没有解决您的问题?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-05
  • 2014-04-10
  • 1970-01-01
  • 2016-02-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多