为什么Deref::deref的返回类型本身是一个引用?

27

我正在阅读Rust的Deref特性的文档:

pub trait Deref {
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
}
deref函数的类型签名对我来说似乎有些反直觉,为什么返回类型是引用?如果引用实现了此特性以便可以被解引用,那么这会产生什么影响呢?
我能想到的唯一解释是,引用并没有实现Deref,但被认为是“原始可解引用的”。然而,那么一个泛型函数应该如何编写才能适用于任何可解引用类型,包括Deref<T>&T呢?

2
这可能会引起你的兴趣 https://www.reddit.com/r/rust/comments/2umad5/the_output_of_the_index_trait_should_be_a_value/ - jtepe
我只是想指出为什么Deref需要返回一个引用。至于引用本身,你可以有一个对引用的引用。那么为什么不想要解引用它们呢?实际上它们都实现了Deref。 - jtepe
@JonasTepe 但如果目标是对引用进行解引用,那么这有什么意义呢?你不是只会收到另一个引用,指向原始引用所指向的同一对象吗? - corazza
1
@jco *x 并不是所谓的调用 deref*x 解引用一个 &-ptr。Deref 允许一个不是引用的对象 假装 成为一个引用,即 Box<T> 可以像 &T 一样运作。请参见 https://github.com/rust-lang/rfcs/blob/master/text/0241-deref-conversions.md。 - Veedrac
2
如果这个特性被称为AsRef,可能会更容易理解。不幸的是,我们已经有一个叫做那个名字的特性了... - Lambda Fairy
显示剩余7条评论
3个回答

19

这意味着引用类型没有实现 Deref

你可以查看所有实现了 Deref 的类型,而 &T 就在这个列表中:

impl<'a, T> Deref for &'a T where T: ?Sized

不太明显的是,当你使用*运算符与实现Deref的对象一起使用时,会应用语法糖。看看这个小例子:

use std::ops::Deref;

fn main() {
    let s: String = "hello".into();
    let _: () = Deref::deref(&s);
    let _: () = *s;
}

error[E0308]: mismatched types
 --> src/main.rs:5:17
  |
5 |     let _: () = Deref::deref(&s);
  |                 ^^^^^^^^^^^^^^^^ expected (), found &str
  |
  = note: expected type `()`
             found type `&str`

error[E0308]: mismatched types
 --> src/main.rs:6:17
  |
6 |     let _: () = *s;
  |                 ^^ expected (), found str
  |
  = note: expected type `()`
             found type `str`

显式调用 deref 返回一个 &str,而操作符 * 返回一个 str。更像是你在调用 *Deref::deref(&s),忽略了暗含的无限递归 (参见文档)

Xirdus 正确地说

如果 deref 返回一个值,它要么是无用的,因为它总是移动出去,要么具有与每个其他函数截然不同的语义

虽然 "无用" 有点强烈;对于实现了 Copy 的类型,它仍然是有用的。
另请参见: 注意,上述所有内容对于 IndexIndexMut 也是有效的。

10
编译器只知道如何解引用&-指针,但它也知道实现 Deref trait 的类型有一个deref()方法,可以用来获取给定对象内部的适当引用。如果您解除引用一个对象,则实际上是先获得引用,然后再解引用它。
如果 deref() 返回一个值,那么它要么没有用因为它总是移动出去,要么具有与任何其他函数截然不同的语义,这不太好。

0
解引用是一个由两个部分组成的操作。第一个部分是获取数据的引用,第二个部分是访问引用后面的数据,这是通过解引用运算符*来完成的。Deref特性只完成了第一个部分,即获取引用并将其提供给我们。然后,解引用运算符*会跟随引用(内存地址),使我们能够访问该内存地址上的数据,这部分实际上是编译器内置的功能。
fn main()
{
    let age: i32 = 90;
    let r_age = &age;       // first part: taking reference
    println!("{}", *r_age); // second part: accessing the reference
}

现在如果我们没有一个引用,我们怎么能够使用解引用运算符*来访问数据呢?
fn main()
{
   let age: i32 = 90;
   //println!("{}", *age);   // will not work, we did not take reference of age.
   println!("{}", *(&age));
}

现在我们可以为自己的类型实现Deref特质。
use std::ops::Deref; 

fn main()
{
   let d = OurOwnType(77);
   println!("{}", *d);
   println!("{}", *(d.deref()));
   println!("{}", *(Deref::deref(&d)));
   println!("{}", *(&d.0));
}

struct OurOwnType(i32);

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

在*d编译器的幕后,它调用了Deref特质中的deref方法,就像d.deref()一样,这会给我们一个引用,然后使用解引用运算符*进行解引用操作,让我们访问数据。在这里,Deref特质完成了第一部分,给我们提供了一个引用。

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