< 返回版块

leedstyh 发表于 2023-07-20 14:22

有如下代码:

fn main() {
    let msg = Message::Hello { id: 110 };

    match msg {
        x @ Message::Hello { id } => println!("{} in {:?}", id, x),
        _ => println!("world"),
    }
}

#[derive(Debug)]
enum Message {
    Hello { id: i32 },
    // World { a: i32, b: i32 },
}

我把World那一行注释了,可以编译通过。但是如果取消注释,那就有会报错,错误如下:

 --> src\main.rs:5:30
  |
2 |     let msg = Message::Hello { id: 110 };
  |         --- move occurs because `msg` has type `Message`, which does not implement the `Copy` trait
...
5 |         x @ Message::Hello { id } => println!("{} in {:?}", id, x),
  |         - value moved here   ^^ value used here after move
  |
help: borrow this binding in the pattern to avoid moving the value
  |
5 |         ref x @ Message::Hello { id } => println!("{} in {:?}", id, x),
  |         +++

请教下这是什么原因?为什么enum里有多个时,会有move的问题。

评论区

写评论
苦瓜小仔 2023-07-22 13:03

如果你交叉发布相同的问题到 URLO,请附上链接。

Confusing @ Pattern matching

如果你觉得这是一个问题,就去 Rust 仓库找 issue 或者在那提问。

#90368: 2021 edition's binding to sub-pattern doesn't work on enum with copyable fields

苦瓜小仔 2023-07-22 10:59

单variant和双variant语义还不一致

我猜可能会有一些考量:回看 copy, single variant 生成的 MIR,会发现 Message { id } 对应于 _3 = ((_1 as Hello).0: i32) ,这与结构体的模式匹配很类似

struct Message {
    id: i32,
}

match msg {
    x @ Message { id } => ...
}

// 生成的 MIR
_1 = Message { id: const 110_i32 };
_3 = (_1.0: i32); // id => _3
_2 = move _1; // x => _2

而双(多) variant 下,首先会识别判别式 (discriminant),即不像 (_1 as Hello) 那么简单,然后才 move 到 x,最后进行其他操作。

如果你觉得这是一个问题,就去 Rust 仓库找 issue 或者在那提问。

作者 leedstyh 2023-07-21 13:54

对双 variant 下的 id(: i32) 的处理类似于 non-copy, single variant 的情况

其实我对这句有点儿不太理解,或者说不太容易接收,我总感觉这改变了语义。

都是Copy的,却按non-copy处理,而且单variant和双variant语义还不一致

--
👇
araraloren: 这看起来应该是个bug,经过分析感觉加了World之后生成代码的顺序变了

araraloren 2023-07-21 10:52

这看起来应该是个bug,经过分析感觉加了World之后生成代码的顺序变了

作者 leedstyh 2023-07-21 03:05

谢谢,涨知识了

--
👇
苦瓜小仔: > 为什么enum里有多个时

与 variant 数量无关,只把 id 换成 String (非 Copy 类型),一样报错

问题出在单 variant 对 Copy 类型的处理不同, 关键的 MIR 代码:

// copy, single variant
debug msg => _1;
debug x => _2;
debug id => _3;

_1 = Message::Hello { id: const 110_i32 };
_3 = ((_1 as Hello).0: i32); // 先 copy id(: i32)
_2 = move _1; // 后 move msg
// non-copy, single variant
debug x => _6;
debug id => _7;

_7 = move ((_1 as Hello).0: std::string::String); // 先 move id(: String)
_6 = move _1; // 后 move msg

这是什么原因?

首先,不要依赖上面对 Copy vs non-Copy 脱糖的细节。

其次,查看 MIR 后,对双 variant 下的 id(: i32) 的处理类似于 non-copy, single variant 的情况,所以出错:即移动 msg 所有权之后移动 id 的所有权导致错误。

最后,在双 variant 下 Copy id 之后移动 msg 所有权的一种做法是:https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=94790f2b7994651e68ab9d9eba480ce8

    match &msg { // 对引用类型进行模式匹配
        &Message::Hello { id } /* Copy */ => println!("{} in {:?}", id, take_msg(msg /* 在分支内获取所有权 */)),
        _ => println!("world"),
    };
苦瓜小仔 2023-07-20 17:44

为什么enum里有多个时

与 variant 数量无关,只把 id 换成 String (非 Copy 类型),一样报错

问题出在单 variant 对 Copy 类型的处理不同, 关键的 MIR 代码:

// copy, single variant
debug msg => _1;
debug x => _2;
debug id => _3;

_1 = Message::Hello { id: const 110_i32 };
_3 = ((_1 as Hello).0: i32); // 先 copy id(: i32)
_2 = move _1; // 后 move msg
// non-copy, single variant
debug x => _6;
debug id => _7;

_7 = move ((_1 as Hello).0: std::string::String); // 先 move id(: String)
_6 = move _1; // 后 move msg

这是什么原因?

首先,不要依赖上面对 Copy vs non-Copy 脱糖的细节。

其次,查看 MIR 后,对双 variant 下的 id(: i32) 的处理类似于 non-copy, single variant 的情况,所以出错:即移动 msg 所有权之后移动 id 的所有权导致错误。

最后,在双 variant 下 Copy id 之后移动 msg 所有权的一种做法是:https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=94790f2b7994651e68ab9d9eba480ce8

    match &msg { // 对引用类型进行模式匹配
        &Message::Hello { id } /* Copy */ => println!("{} in {:?}", id, take_msg(msg /* 在分支内获取所有权 */)),
        _ => println!("world"),
    };
1 共 6 条评论, 1 页