Rust中使用`let`语句进行模式匹配的工作原理

3
为什么片段`pattern_matching_2`和`pattern_matching_3`可以工作,而`pattern_matching_1`不能?编译器建议 `let &x = &foo` 移动了字符串 `hello`,我知道这一点,但我不明白问题出在哪里 -- 我根本没有使用 `foo`,而且编译器对于 `pattern_matching_3` 也没有任何抱怨,但它同样移动了字符串 `hello`。
这些示例代码摘自此答案,但并未回答我的问题。
代码片段:
fn pattern_matching_1() {
    let foo = String::from("hello");
    let &x = &foo;
    println!("{}", x);
}

fn pattern_matching_2() {
    let foo = 12;
    let &x = &foo;
    println!("{}", x);
}

fn pattern_matching_3() {
    let foo = String::from("hello");
    let x = foo;
    println!("{}", x);
}

pattern_matching_1的编译器错误:

error[E0507]: cannot move out of a shared reference
  --> src/main.rs:26:14
   |
26 |     let &x = &foo;
   |         --   ^^^^
   |         ||
   |         |data moved here
   |         |move occurs because `x` has type `String`, which does not implement the `Copy` trait
   |         help: consider removing the `&`: `x`
2个回答

1
编译器建议使用let &x = &foo移动字符串hello,我知道这一点,但不知道问题出在哪里。
问题在于你给编译器一个不可变引用到一个变量(&foo),然后要求它移动底层数据。这是不允许不可变引用进行的操作。
为了更明确,将移出部分提取到另一个函数中:
fn pattern_matching_1_helper(r: &String) {
    let &x = r;
    println!("{}", x);
}

fn pattern_matching_1() {
    let foo = String::from("hello");
    pattern_matching_1_helper(&foo);
}

我希望你能理解,pattern_matching_1_helper 无法单独编译。但在你的代码中,合并版本与其实际上没有什么不同。 pattern_matching_2 可以编译,因为 i32Copy 类型,所以编译器不需要移动它。

我不完全同意原始版本并没有什么区别。在你的例子中,很明显 r 对整个函数都是生命周期的,所以我们不能将底层数据移到外面。而在原始例子中,引用只是一个临时的,只在语句的生命周期内存在,因此移动底层数据不会在原始情况下创建任何悬空引用,并且原始代码实际上是安全的(尽管无意义)。 - Sven Marnach
澄清一下,我知道从借用检查器的角度来看,你的代码是相同的——引用在语句结束之前一直存在,因此你不能在语句中移动数据。然而,这种情况之所以如此,并不是因为情况“没有区别”,而是因为有人决定它应该是这样工作的。 - Sven Marnach
@SvenMarnach 引用的生命周期并不重要。重要的是编译器不会通过引用移动值。你可以从一个变量中移动,但你永远不能通过引用移动。 - Sebastian Redl
让我试着重新表述一下以澄清我的观点。Rust有一个基本规则,即引用永远不会变得无效。因此,这个答案中的代码是无效的:移动字符串后,r将成为无效引用。原始帖子中的代码没有这个问题,即使Rust允许在那种情况下移动字符串,我们也不会得到任何无效的引用。(我在这里并不是在纠缠细节 - 我认为这种区别实际上是造成混淆的原因。) - Sven Marnach
抱歉,我无法理解“然后要求它移动底层数据”是一个问题。毕竟,我不再使用已移动的数据(foo)。为什么这是个问题?谢谢。 - Kevin
显示剩余2条评论

0
fn pattern_matching_3() {
    let foo = String::from("hello");
    let x = foo;
    println!("{}", x);
}

这个工作是因为foo被移动到x中,所以foo不再是一个可用的变量。只要那个拥有它的变量不再使用,从拥有它的变量中移动数据是可以的。
fn pattern_matching_1() {
    let foo = String::from("hello");
    let &x = &foo;
    println!("{}", x);
}

这个不起作用是因为 foo 仍然是字符串的所有者,但你正在尝试将数据从 x 中移出,而它只是借用了它。

pattern_matching_2 起作用是因为 i32Copy,所以没有移动任何东西。


我不太明白你所说的“但是你正在尝试将数据从x中移出”的意思。根据这本书所述,let PATTERN = EXPRESSION;let x = foo;应该等同于let &x = &foo;。为什么let &x = &foo;是错误的? - Kevin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接