Rust中自动取消引用是如何工作的?

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

How does automatic de-referencing work in Rust?

问题

我正在尝试学习Rust,并且有些困惑为什么在某些情况下需要解引用指针而在其他情况下不需要。在网上寻找答案时,我发现Rust具有自动解引用功能,但我找不到关于这个主题的详细信息。有人可以解释一下这是如何工作的吗?

为了更具体,这是我遇到这个问题的一个示例代码。

fn mode(v: &mut Vec<i32>) -> Option<i32> {
    let mut map: HashMap<i32, i32> = HashMap::new();
    let mut max = (0, 0);
    if v.is_empty() {
        return None;
    } else {
        ();
    }
    for i in v {
        let count = map.entry(*i).or_insert(0);
        *count += 1;
    }
    for (k, v) in map.iter() {
        if v > &max.1 {
            max = (*k, *v);
        } else {
            ();
        }
    }
    Some(max.0)
}

在这里,显然没有必要解引用向量 v 的任何地方,但我们需要解引用其他多个变量,如第75行的 i,我不明白为什么需要这样做。

英文:

I am trying to learn Rust and am having some trouble understanding why we need to dereference pointers in some cases and not in others. Fishing for answers online, I found out that Rust has automatic dereferencing, but I couldn't find much on the topic. Can someone explain to me how this exactly works?

To make it more specific, here is a sample code where I encountered this problem.

fn mode(v: &amp;mut Vec&lt;i32&gt;) -&gt; Option&lt;i32&gt; {
    let mut map: HashMap&lt;i32, i32&gt; = HashMap::new();
    let mut max = (0, 0);
    if v.is_empty() {
        return None;
  
    } else {
        ();
  
    }
    for i in v {
        let count = map.entry(*i).or_insert(0);
        *count += 1;
  
    }
    for (k, v) in map.iter() {
        if v &gt; &amp;max.1 {
            max = (*k, *v);
           } else {
            ();
  
        }
  
    }
    Some(max.0)

Here, there is apparantely no need to dereference vector v anywhere, but we need to dereference several other variables such as i in line 75, and I do not understand why.

答案1

得分: 4

以下是翻译好的内容:

最简单的方法来说明这一点是在示例代码x.foo(y)的上下文中。

这里的x是_接收者。鉴于x的类型,编译器必须找出foo是什么函数。它试图在x的类型上定位foo,并且通过_自动解引用_ x,这就是你所询问的。这个过程考虑了x的类型以及通过解引用x尽可能多次遇到的每个类型。(它还考虑了&amp;和可能是&amp;mut这些类型的变体,这取决于x或其引用类型的可变性。)

如果确切地找到一个foo,那么调用将被转换为T::foo(z, y),其中Tfoo方法所在的类型,而z是任何一系列解引用(加上最后一个可选的&amp;&amp;mut)的结果为T&amp;T&amp;mut T,根据T::foo的要求。

那么y呢?参数经历强制类型转换,这是一个完全不同的事情。链接的文档列出了所有可能的强制类型转换,出于简洁起见,我将省略它们,但值得注意的是&amp;TT&amp;mut TT都不包括在内,其中之一将需要使map.entry(i)在你的示例代码中工作。因此,明确的解引用是必需的。

请注意,强制类型转换列表中包含的一个项是:

> 如果T实现了Deref&lt;Target = U&gt;,则&amp;T&amp;mut T转换为&amp;U

注意,这个操作实际上_不会解引用引用_,而是_将引用转换为另一种类型的引用!_也就是说,强制类型转换是&amp;T&amp;U,而不是&amp;TU。这就是允许你将&amp;String传递给期望&amp;str的参数的原因,例如(String实现了Deref&lt;Target = str&gt;),但这个规则不提供从&amp;mut i32i32的强制类型转换,这是使map.entry(i)工作所需的。


for i in v是一个完全不同的情况;这起作用是因为&amp;mut Vec&lt;T&gt;实现了IntoIterator


因此,为什么你不必解引用v的答案是因为你在接收者位置使用它,这里会发生自动解引用。函数参数则经历强制类型转换,具有完全不同的一套规则。

现在说到这一点,在你的代码中,v已经是对Vec的引用,因此v.is_empty()也可以通过强制类型转换来解释,因为这个方法来自切片,而Vec会自动解引用为切片。然而,总体观点仍然成立——你不能混淆自动解引用和解引用强制类型转换。这是一个例子,在其中它们恰好做同样的事情,但在许多其他情况下,它们不会这样做。

英文:

The easiest way to illustrate this is within the context of the sample code x.foo(y).

x here is the receiver. Given the type of x, the compiler has to figure out what function foo is. It attempts to locate foo on x's type, and additionally by auto-dereferencing x, which is what you are asking about. This process considers the type of x as well as every type that is encountered by dereferencing x as many times as possible. (It also considers &amp; and possibly &amp;mut variants of these types, depending on the mutability of x or its reference type.)

If exactly one foo is found, then the call is transformed to T::foo(z, y) where T is the type the foo method was located on, and z is whatever sequence of dereferences (plus one final, optional &amp; or &amp;mut) results in a T, &amp;T, or &amp;mut T, as required by T::foo.

What about y, though? Arguments undergo coercion, which is a different thing altogether. The linked documentation lists all of the possible coercions, which I will omit for the sake of brevity, but notably absent are &amp;T to T and &amp;mut T to T, one of which would be required to make map.entry(i) work in your sample code. The explicit dereference is therefore required.

Note that one of the items present in the list of coercions is:

> &amp;T or &amp;mut T to &amp;U if T implements Deref&lt;Target = U&gt;.

Observe that this doesn't actually dereference a reference but rather converts the reference to another type of reference! That is, the coercion is &amp;T to &amp;U, not &amp;T to U. This is what allows you to give &amp;String to an argument that expects a &amp;str, for example (String implements Deref&lt;Target = str&gt;), but this rule doesn't provide coercion from &amp;mut i32 to i32 as would be required to make map.entry(i) work.


for i in v is a completely different scenario; this works simply because &amp;mut Vec&lt;T&gt; implements IntoIterator.


So the answer as to why you don't have to dereference v is because you are using it in receiver position, where auto-dereference happens. Function arguments instead undergo coercion, which has an entirely different set of rules.

Now having said that, in your code v is already a reference to a Vec, and so v.is_empty() could be explained through coercion as well, as this method comes from slices, to which Vec automatically dereferences. However, the overall point still stands -- you cannot conflate auto-dereferencing with deref coercion. This is one instance where they would happen to do the same thing, but in many other cases they would not.

huangapple
  • 本文由 发表于 2023年5月28日 12:48:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/76349973.html
匿名

发表评论

匿名网友

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

确定