在模式匹配期间防止移动语义

问题描述 投票:0回答:2

我这里有一个愚蠢的例子,只是为了演示我在使用另一个库和模式匹配时遇到的问题。

struct Person {
    name: String,
    age: i32,
    choice: Choices
}

#[derive(Debug)]
enum Choices {
    Good,
    Neutral,
    Evil
}

fn find(p: Person) {
    match (p.choice, p.age) {
        (Choices::Good, a) if a < 80 => {
            announce(p);
        }
        (_, a) if a >= 80 => {
            println!("You're too old to care.");
        }
        _ => {
            println!("You're not very nice!")
        }
    }
}

fn announce(p: Person) {
    println!("Your name is {}. You are {:?}.", p.name, p.choice);
}

fn main() {
    let p = Person {
                name: "Bob".to_string(),
                age: 20,
                choice: Choices::Good
            };
    find(p);
}

现在的问题似乎是在模式匹配期间,移动语义将启动并接管我的 Person 中的内部结构(事物)的所有权。

当我要将该人转移到下一个方法时,我不能,因为它已被部分移动。

Compiling match v0.1.0 (file:///home/jocull/Documents/Projects/Rust/learn/match)
src/main.rs:17:13: 17:14 error: use of partially moved value: `p`
src/main.rs:17          announce(p);
                                 ^
src/main.rs:15:9: 15:17 note: `p.choice` moved here because it has type `Choices`, which is non-copyable
src/main.rs:15  match (p.choice, p.age) {
                       ^~~~~~~~
error: aborting due to previous error
Could not compile `match`.

我的直觉告诉我,我需要让 Rust 停止通过使用某种引用或借用来移动值。在这种情况下,我可以更改我的方法签名以进行借用,但对于某些库,你并不总是能够做到这一点。 (在这种情况下我正在尝试处理hyper...)

有没有办法让

match
在匹配过程中使用引用而不是移动值?谢谢!

rust pattern-matching move-semantics
2个回答
26
投票

为什么?

当你制作元组时

(p.choice, p.age)

memcpy
p.choice
p.age
来自你的
Person

p.age
执行此操作是可以的,因为它是
Copy
类型 - 您可以在
memcpy
后继续使用旧值。

p.choices
属于
Choices
类型,不是
Copy
。这意味着
memcpy
被视为“移动”,因此旧值不可用。这意味着
p
处于无效状态,因此您无法对其调用
announce

解决方案#1

由于

Choices
是一个微不足道的
enum
,所以你可以直接
#[derive(Copy, Clone)]
。这意味着您可以继续使用旧的
p.choices

如果你只能安全地制作

Choices
Clone
,那么你就必须在
clone
match
来代替。

解决方案#2

您可以通过

参考
采取p.choices

match (&p.choice, p.age) {
    (&Choices::Good, a) if a < 80 => {
        announce(p);
    }
    ...
}

这只有效,因为

&Choices::Good
完全匹配,因此可以放弃借用。如果你有的话

match (&p.choice, p.age) {
    (&x, a) if a < 80 => {
        announce(p);
    }
    ...
}

借用仍将处于活动状态,因此调用

announce(p)
时的移动将会失败 - 该移动将使活动的借用变量无效。

注释

您要搬到这里进行大量的工作 - 传递一些参考资料会更加灵活!

announce
没有理由消耗
Person
- 它只需要稍微看一下即可。当您可以获取参考时,按值获取仅适用于小型
Copy
类型。

请注意,让

announce
获取引用意味着
match
也可以保留
p
内部的引用,这使其适用范围更广。

to_string
主要用于非字符串对象。
into
to_owned
更快,
into
也更短。

struct Person {
    name: String,
    age: i32,
    choice: Choices
}

#[derive(Copy, Clone, Debug)]
enum Choices {
    Good,
    Neutral,
    Evil
}

fn find(p: &Person) {
    match (p.choice, p.age) {
        (Choices::Good, a) if a < 80 => {
            announce(p);
        }
        (_, a) if a >= 80 => {
            println!("You're too old to care.");
        }
        _ => {
            println!("You're not very nice!")
        }
    }
}

fn announce(p: &Person) {
    println!("Your name is {}. You are {:?}.", p.name, p.choice);
}

fn main() {
    let p = Person {
        name: "Bob".into(),
        age: 20,
        choice: Choices::Good
    };

    find(&p);
}

1
投票

所以我再次尝试更改新的代码:)

在您当前的代码中,如果您使用借用而不是移动匹配,则它可以工作。

p.age
不需要它,只是因为它是一个原始类型,并且原始类型实现了
Copy
特质

但是选项没有实现复制特征,因此它们在比赛中被移动。这会导致您拨打电话时他们无法接通

announce()

match (&p.choice, p.age) {
        (&Choices::Good, a) if a < 80 => {
            announce(p);
        }
        ...
}

它消除了部分移动的错误。我想这是因为你在比赛中改变了选择。但选择是人的一部分,所以它被部分移动了。

我对 Rust 的了解还不够,无法真正解释它为什么有效,所以如果你能添加一些有用的东西,请这样做

© www.soinside.com 2019 - 2024. All rights reserved.