为什么匹配语句对引用类型的要求比函数参数更严格?

huangapple go评论59阅读模式
英文:

Why are match statements more picky about reference types than function parameters?

问题

在Rust中,经常会看到以&str作为参数的函数。

fn foo(bar: &str) {
    println!("{}", bar);
}

在调用这种函数时,通过引用传递String作为参数是完全可以的。

let bar = String::from("bar");
foo(&bar);

严格来说,传递的参数是&String,而函数期望的是&str,但Rust(正如人们期望的那样)会自动处理,一切都能正常工作。然而,对于匹配语句,情况并非如此。如果我尝试在匹配语句中像以前一样使用相同的bar变量,简单的用法将无法编译:

match &bar {
    "foo" => println!("foo"),
    "bar" => println!("bar"),
    _ => println!("Something else")
};

Rustc会抱怨它期望一个&str,但收到了一个&String。问题和解决方法都非常明显:只需更明确地使用.as_str()来借用bar。但这带来了一个真正的问题:为什么会这样呢?

如果Rust可以在函数参数的情况下轻松地将&String转换为&str,那么为什么不能在匹配语句中做同样的事情?这是类型系统的限制结果,还是在匹配语句中使用更复杂的借用存在隐藏的不安全性?或者这只是一种生活质量改进在某些地方得到了集成,而在其他地方没有?我相信对类型系统有深刻了解的人会有答案,但关于这个行为小细节似乎在互联网上没有太多信息。

英文:

In Rust, one often sees functions that take &str as a parameter.

fn foo(bar: &str) {
    println!("{}", bar);
}

When calling functions like this, it is perfectly fine to pass in a String as an argument by referencing it.

let bar = String::from("bar");
foo(&bar);

Strictly speaking, the argument being passed is an &String and the function is expecting an &str, but Rust (as one would hope) just figures it out and everything works fine. However, this is not the case with match statements. If I try and use the same bar variable as before in a match statement, the naive usage will not compile:

match &bar {
    "foo" => println!("foo"),
    "bar" => println!("bar"),
    _ => println!("Something else")
};

Rustc complains that it was expecting an &str but received an &String. The problem and solution are both very obvious: just borrow bar more explicitly with .as_str(). But this brings me to the real question: why is this the case?

If Rust can figure out that an &String trivially converts to an &str in the case of function arguments, why can't it do the same thing with match statements? Is this the result of a limitation of the type system, or is there hidden unsafety in fancier borrowing with match statements? Or is this simply a case of a quality of life improvement getting integrated into some places but not others? I'm sure someone knowledgeable about type systems has an answer, but there seems to be very little information about this little quirk of behavior on the internet.

答案1

得分: 5

这个代码不起作用的技术原因是因为 match 的条件表达式不是强制转换位置。如您在 foo(&bar) 示例中所示,函数参数可以是潜在的强制转换位置;并且它允许您将 &String 传递为 &str,这是因为存在 Deref 强制转换。

它不是强制转换位置的可能原因之一是因为没有清晰的类型应该进行强制转换。在您的示例中,您希望它是 &str,因为它与字符串字面量匹配,但是对于以下情况怎么办:

match &string_to_inspect {
    "special" => println!("this is a special string"),
    other => println!("this is a string with capacity: {}", other.capacity()),
};

人们希望 match 行为类似于 &str 以匹配字面量,但因为匹配的是 &String,人们也希望 other 也是 &String。如何同时满足这两个要求?下一个合理的步骤将是使每个模式根据需要进行强制转换,这已经被广泛期望......但它会引发一系列问题,因为 Deref 是用户可定义的。请参阅 Rust 语言团队的deref patterns以获取更多信息。

英文:

The technical reason it doesn't work is because the match scrutinee is not a coercion site. Function arguments, as shown in your foo(&bar) example, are possible coercion sites; and it allows you to pass a &String as a &str because of Deref coercion.

A possible reason why its not a coercion site is that there's no clear type that it should be coerced to. In your example you'd like it to be &str since that matches the string literals, but what about:

match &string_to_inspect {
    "special" => println!("this is a special string"),
    other => println!("this is a string with capacity: {}", other.capacity()),
};

One would like the match to act like &str to match the literal, but because the match is on a &String one would expect other to be a &String as well. How to satisfy both? The next logical step would be for each pattern to coerce as required, which has been much desired... but it opens a whole can of worms since Deref is user-definable. See deref patterns from the Rust lang-team for more info.

答案2

得分: 0

https://doc.rust-lang.org/reference/type-coercions.html 说道:

强制转换位置

强制转换只能在程序中的特定强制转换位置发生;这些位置通常是期望的类型显式或可以通过明确类型的传播来派生的地方(无需进行类型推断)。可能的强制转换位置包括:

  • [...]

  • 函数调用的参数

    被强制转换的值是实际参数,它被强制转换为形式参数的类型。

但不包括 match 表达式的被匹配值。

强制转换类型

下列类型之间允许进行强制转换:

  • [...]
  • 如果 T 实现了 Deref<Target = U>,则可以将 &T&mut T 强制转换为 &U
英文:

https://doc.rust-lang.org/reference/type-coercions.html says:
> # Coercion sites
> A coercion can only occur at certain coercion sites in a program; these are typically places where the desired type is explicit or can be derived by propagation from explicit types (without type inference). Possible coercion sites are:
> - [...]
> - Arguments for function calls
>
> The value being coerced is the actual parameter, and it is coerced to the type of the formal parameter.

but not a match scrutinee.

> # Coercion types
> Coercion is allowed between the following types:
> - [...]
> - &amp;T or &amp;mut T to &amp;U if T implements Deref&lt;Target = U&gt;.

huangapple
  • 本文由 发表于 2023年2月7日 03:49:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/75365910.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定