为什么我们需要为&T实现Deref trait的默认行为?

3
实现智能指针的Deref使得访问它们背后的数据变得方便,这也是它们实现Deref的原因。另一方面,关于Deref和DerefMut的规则是专门为适应智能指针而设计的。因此,为避免混淆,只应该为智能指针实现Deref。我理解它的有用性,但我不知道这种Deref trait的全面实现有什么用处。
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_deref", issue = "88955")]
impl<T: ?Sized> const Deref for &T {
    type Target = T;

    #[rustc_diagnostic_item = "noop_method_deref"]
    fn deref(&self) -> &T {
        *self
    }
}

正如书中所说https://doc.rust-lang.org/book/ch15-02-deref.html#treating-a-type-like-a-reference-by-implementing-the-deref-trait

没有Deref trait,编译器只能解引用 & 引用。deref方法使编译器能够获取任何实现Deref trait的类型的值,并调用deref方法以获取它知道如何解引用的 & 引用。

如果编译器知道如何解引用,为什么我们需要对 &T 类型进行 Deref trait 的全局实现呢?

Rust 的自动解引用规则是什么? 这个问题并没有回答我的问题,因为我的问题是这个全局实现的用途是什么。

为什么Deref::deref的返回类型本身是一个引用?这个问题也没有解决我的疑惑。在第一个答案中,作者没有解释为什么存在对&T类型的Deref trait的泛化实现。

我添加了第一个答案的作者的代码,并进行了自己的修改

Rust playground https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=25387e87ea7a7ef349df9e9b6671f6e7

我添加了一些注释,以确保我真正理解它

use std::ops::Deref;
use std::rc::Rc;

fn foo<T: Deref<Target = i32>>(t: T) {
    println!("{}", *t);
    print_type_of(&t);
    print_type_of(&(*t));
    println!("-----------------");
}

fn main() {
    // Ok, because &i32 implements Deref<Target = i32>, so deref() return &i32 and the compiler can deref it again to get i32
    // Thanks to the blanket implementation
    foo(&100i32);
    // No, because &&i32.deref() return &&i32, so the compiler can't deref it twice to get i32
    foo(&&100i32);
    // Ok, because Box<i32> implements Deref<Target = i32>, so deref() return &i32 and the compiler can deref it again to get i32
    // But this has nothing to do with the blanket implementation of Deref for &T
    // Because Box is not a reference type
    foo(Box::new(42i32));
    // Ok, because Rc<i32> implements Deref<Target = i32>, so deref() return &i32 and the compiler can deref it again to get i32
    foo(Rc::new(5i32));
}

fn print_type_of<T>(_: &T) {
    println!("{}", std::any::type_name::<T>())
}

我不确定我理解你的回答


我认为rustc_diagnostic_item是用于Clippy分析的注解。但是我找不到任何依赖它的Clippy规则。也许它们都被更具体的提示所替换(这个deref是不必要的,这个cast是不必要的...),现在这个无操作的注释本身就是一个无操作的注释;-)。 - rodrigo
我认为关闭这个问题是错误的。@Yang,这里的重点是一些内置类型的特质实现是特殊的。对于像&TBox<T>这样的内置类型的解引用,编译器知道如何在内部执行。出于凝聚力的原因,标准库定义了这些实现,以便特质解析按预期工作,但它们的主体实际上从未被执行。情况与1+1类似,其中有一个std::ops::Add的实现,适用于所有以1+1为基础定义的数字类型。 - user2722968
我已经编辑了这个问题,但它仍然关闭着。 - Yang
我在问题中加入了自己的理解,只是为了确认我是否真正理解了它。请再看一遍。 - Yang
2个回答

2

为了实现泛型,&T也存在一个Deref实现。

尽管编译器天生就知道如何取消引用引用,因为它们内置于语言中,但Deref实现仍应存在,以便&T可以在需要Deref的上下文中使用。考虑以下函数:

fn foo<T: Deref<Target = i32>>(t: T) {
    println!("{}", *t);
}

playground上可以看到它的工作原理;

它可以接受引用、BoxRc等。如果引用没有实现Deref,那么你可以直接使用解引用运算符*,但你无法将其传递给这个函数进行解引用。这样做是不一致和令人困惑的。


我不确定你的例子想要证明什么;它不能在标准库外编译,并且你对类型的评估是错误的(Self&T,所以self的类型是&&T,当被解引用时返回&T,与返回类型匹配)。实现一个等效的trait也能正常工作:

trait Deref {
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
}

impl<T: ?Sized> Deref for &T {
    type Target = T;
    fn deref(&self) -> &T {
        *self
    }
}

playground上查看它的运行情况;


非常感谢您抽出时间回答我的问题,我不确定理解Deref的含义,我想问一下:对于&T,Deref会返回&T本身,这是没有意义的吗?它只是一个标记吗? - Yang
谢谢回复,我在源代码中看到了:#[rustc_diagnostic_item = "noop_method_deref"]。这是否意味着该方法不会被调用?因为deref()返回自身(接受&T -> 然后返回&T),所以调用它也是无用的吗? - Yang
我在问题中加入了自己的理解,只是为了确认我是否真正理解了它。请再看一遍。 - Yang
@Yang 我不确定,但考虑到它是一个“诊断”属性,更有可能与错误消息的生成方式有关。 - kmdreko
@Yang,我不知道你的编辑意味着什么。你是在问一个新问题吗?还是有一些具体的部分让你不确定?请注意让你的问题更加专注;你最初的问题是关于&T实现Deref的。而你已经忽略了链接的答案,这些答案应该已经缓解了你的其他疑虑。 - kmdreko
显示剩余2条评论

0
还有另一个原因导致了全面实施的存在:一致性。
一致性意味着对于任何给定的特性和类型,都有一个特定的实现适用。 一致性 Rust的解引用操作是编译器内置的。编译器必须确保解引用操作始终保持一致统一。
当我们解引用一个&i32时,我们可以访问i32。因此,在解引用&MyOwnType时,它应该让我们访问MyOwnType:这确保了Rust中所有其他类型(类型的引用)的一致性。
由于一致性规则 - 作为实现Deref&T,我们不能为&MyOwnType实现Deref特性。尝试这样做将生成以下错误:
error[E0119]: conflicting implementations of trait `Deref` for type `&MyOwnType`
 --> src/main.rs:9:1
  |
9 | impl std::ops::Deref for &MyOwnType
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: conflicting implementation in crate `core`:
          - impl<T> Deref for &T
            where T: ?Sized;

For more information about this error, try `rustc --explain E0119`.

Let's pretend impl Deref for &T didn't exist, and Rust didn't care about coherence. It would be possible for someone to come along and implement something like this:

struct Evil(i32);
impl std::ops::Deref for &'_ Evil {
    type Target = i32;
    fn deref(&self) -> &Self::Target { &self.0 } 
} 

Now, depending on whether you directly dereference an &Evil, or do so via the Deref trait, you get different results. This means you can't take code that includes dereferences and make it generic without it potentially breaking. It also makes the code harder to understand. DanielKeep's answer on Rust User Forum


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